ProbabilityPewter 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ProbabilityPewter/__init__.py +30 -0
- ProbabilityPewter/calculator/__init__.py +5 -0
- ProbabilityPewter/calculator/bayes.py +46 -0
- ProbabilityPewter/calculator/combiner.py +143 -0
- ProbabilityPewter/calculator/series.py +63 -0
- ProbabilityPewter/roll_stats/__init__.py +7 -0
- ProbabilityPewter/roll_stats/combat.py +715 -0
- ProbabilityPewter/roll_stats/compare.py +135 -0
- ProbabilityPewter/roll_stats/dice.py +116 -0
- ProbabilityPewter/visualiser/__init__.py +6 -0
- ProbabilityPewter/visualiser/distributions.py +721 -0
- probabilitypewter-0.5.0.dist-info/METADATA +253 -0
- probabilitypewter-0.5.0.dist-info/RECORD +16 -0
- probabilitypewter-0.5.0.dist-info/WHEEL +5 -0
- probabilitypewter-0.5.0.dist-info/licenses/LICENSE +21 -0
- probabilitypewter-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Docstring for ProbabilityPewter package
|
|
3
|
+
========================================
|
|
4
|
+
|
|
5
|
+
This package provides tools for simulating and visualising probability, primarily aimed at RPGs but also useful for other applications.
|
|
6
|
+
It includes functions for rolling dice, generating random statistics, and visualizing probability distributions.
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.5.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
#Reminder: list functions here that are main features of the package, for easier access.
|
|
14
|
+
# For more niche functions or helper functions, users can do a full import from the submodules.
|
|
15
|
+
from .calculator import combined_prob
|
|
16
|
+
from .roll_stats import rpg_dice, prob_table, combat_odds
|
|
17
|
+
from .visualiser import plot_dice, plot_normal, plot_combat
|
|
18
|
+
|
|
19
|
+
#Finally, we can define __all__ to list the public API of the package.
|
|
20
|
+
# This is optional, but it can help with tools like autocomplete in IDEs, and it also serves as documentation for users about what the main features of the package are.
|
|
21
|
+
__all__ = [
|
|
22
|
+
"__version__",
|
|
23
|
+
"combined_prob",
|
|
24
|
+
"rpg_dice",
|
|
25
|
+
"prob_table",
|
|
26
|
+
"combat_odds",
|
|
27
|
+
"plot_dice",
|
|
28
|
+
"plot_normal",
|
|
29
|
+
"plot_combat",
|
|
30
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Docstring for the bayes module
|
|
3
|
+
|
|
4
|
+
This module provides functionality to update probabilities using Bayes' theorem.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def bayes_updater(prior_A, likelihood_B_given_A, likelihood_B_given_not_A):
|
|
10
|
+
"""Calculate the posterior probability of A given B, P(A|B), using Bayes' theorem.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
-----------
|
|
14
|
+
prior_A : float
|
|
15
|
+
Prior probability P(A) - between 0 and 1.
|
|
16
|
+
likelihood_B_given_A : float
|
|
17
|
+
Probability of B given A, P(B|A) - between 0 and 1.
|
|
18
|
+
likelihood_B_given_not_A : float
|
|
19
|
+
Probability of B given not A, P(B|not A) - between 0 and 1.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
--------
|
|
23
|
+
float
|
|
24
|
+
Posterior probability P(A|B) - between 0 and 1.
|
|
25
|
+
"""
|
|
26
|
+
values = {
|
|
27
|
+
"prior_A": prior_A,
|
|
28
|
+
"likelihood_B_given_A": likelihood_B_given_A,
|
|
29
|
+
"likelihood_B_given_not_A": likelihood_B_given_not_A,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for name, value in values.items():
|
|
33
|
+
if not isinstance(value, (int, float)):
|
|
34
|
+
raise TypeError(f"{name} must be numeric, got {type(value).__name__}.")
|
|
35
|
+
if value < 0 or value > 1:
|
|
36
|
+
raise ValueError(f"{name} must be between 0 and 1.")
|
|
37
|
+
|
|
38
|
+
prior_not_A = 1 - prior_A
|
|
39
|
+
numerator = likelihood_B_given_A * prior_A
|
|
40
|
+
denominator = numerator + (likelihood_B_given_not_A * prior_not_A)
|
|
41
|
+
|
|
42
|
+
if denominator == 0:
|
|
43
|
+
raise ValueError("Denominator is zero, check input probabilities.")
|
|
44
|
+
|
|
45
|
+
posterior_A_given_B = numerator / denominator
|
|
46
|
+
return posterior_A_given_B
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Docstring for the combiner module
|
|
3
|
+
|
|
4
|
+
This module provides functionality to calculate combined probabilities and outcomes.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _round_sig(val, sig=10):
|
|
12
|
+
"""Round val to sig significant figures, preserving very small values."""
|
|
13
|
+
if val == 0:
|
|
14
|
+
return 0.0
|
|
15
|
+
magnitude = math.floor(math.log10(abs(val)))
|
|
16
|
+
return round(val, sig - 1 - magnitude)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _to_decimal_str(val):
|
|
20
|
+
"""Format a float as a plain decimal string without scientific notation."""
|
|
21
|
+
if val == 0:
|
|
22
|
+
return "0"
|
|
23
|
+
magnitude = math.floor(math.log10(abs(val)))
|
|
24
|
+
decimal_places = max(0, 10 - 1 - magnitude)
|
|
25
|
+
formatted = f"{val:.{decimal_places}f}"
|
|
26
|
+
if "." in formatted:
|
|
27
|
+
formatted = formatted.rstrip("0").rstrip(".")
|
|
28
|
+
return formatted
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def combined_prob(A=0.5, B=0.33, event="A&B", output_scale="prob"):
|
|
32
|
+
"""
|
|
33
|
+
Calculate the combined probability of independent events, like A and B.
|
|
34
|
+
The syntax for the event parameter specifies the relationship between events. For example: "A&B" means the probability that both A and B occur, while "A/B" means that either A or B occurs.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Parameters:
|
|
38
|
+
-----------
|
|
39
|
+
A : float or str
|
|
40
|
+
Probability of event A, either as a decimal (0-1), percentage (0-100), or odds ("X:Y")
|
|
41
|
+
B : float or str
|
|
42
|
+
Probability of event B, either as a decimal (0-1), percentage (0-100), or odds ("X:Y")
|
|
43
|
+
event : str
|
|
44
|
+
Syntax for the event combination:
|
|
45
|
+
- "A&B" or "A∩B": AND/intersection P(A∩B) = P(A) * P(B) # both A and B occur, assuming independence
|
|
46
|
+
- "A/B" or "A∪B": OR/union P(A∪B) = P(A) + P(B) - P(A∩B) # either A or B or both occur
|
|
47
|
+
- "!A" or "¬A": NOT/complement P(¬A) = 1 - P(A) # A does not occur
|
|
48
|
+
- "A^B" or "A⊕B": XOR P(A⊕B) = P(A) + P(B) - 2*P(A∩B) # either A or B occurs, but not both
|
|
49
|
+
- "A-B" or "A∖B": Difference P(A∖B) = P(A) - P(A∩B) # A occurs but not B (instead of just: B not occuring)
|
|
50
|
+
- "none(A, B)": P(none) = ∏(1-P(i)) # neither A nor B occur
|
|
51
|
+
- "atleast1(A, B)": P(at least one) = 1 - P(none) # at least one of A or B occurs
|
|
52
|
+
output_scale : str
|
|
53
|
+
Output format:
|
|
54
|
+
"prob" (0-1), "percent" (0-100%), or "odds" (X:Y)
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
--------
|
|
58
|
+
float or str
|
|
59
|
+
Combined probability in requested scale
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Convert inputs to probability scale (0-1) by auto-detecting format:
|
|
63
|
+
# "X:Y" string -> odds, value > 1 -> percent (0-100), otherwise -> prob (0-1)
|
|
64
|
+
def to_prob(val):
|
|
65
|
+
if isinstance(val, str) and ":" in val:
|
|
66
|
+
x, y = map(float, val.split(':'))
|
|
67
|
+
return x / (x + y)
|
|
68
|
+
if isinstance(val, str) and "%" in val:
|
|
69
|
+
return float(val.strip('%')) / 100
|
|
70
|
+
elif val > 1:
|
|
71
|
+
return val / 100
|
|
72
|
+
return val
|
|
73
|
+
|
|
74
|
+
def from_prob(val, out_scale=output_scale):
|
|
75
|
+
val = _round_sig(val) # remove floating point noise without destroying very small values
|
|
76
|
+
if out_scale == "percent" or out_scale == "%" or out_scale == "perc" or out_scale == "percentage":
|
|
77
|
+
return _to_decimal_str(_round_sig(val * 100)) + "%"
|
|
78
|
+
elif out_scale == "odds":
|
|
79
|
+
if val == 0:
|
|
80
|
+
return "0:1"
|
|
81
|
+
elif val == 1:
|
|
82
|
+
return "1:0"
|
|
83
|
+
if val < 0.001:
|
|
84
|
+
# Very small probability: Fraction.limit_denominator(1000) would round to 0, so compute directly
|
|
85
|
+
denom = int(_round_sig(1 / val, sig=3))
|
|
86
|
+
return f"1:{denom}"
|
|
87
|
+
# Convert to simplified integer odds A:B (e.g. 0.25 -> "1:3") expressing the ratio of favorable outcomes to unfavorable outcomes
|
|
88
|
+
frac = Fraction(val).limit_denominator(1000)
|
|
89
|
+
a, b = frac.numerator, frac.denominator - frac.numerator
|
|
90
|
+
return f"{a}:{b}"
|
|
91
|
+
return val
|
|
92
|
+
|
|
93
|
+
p_a = to_prob(A)
|
|
94
|
+
p_b = to_prob(B)
|
|
95
|
+
|
|
96
|
+
event = event.strip()
|
|
97
|
+
|
|
98
|
+
# AND / Intersection
|
|
99
|
+
if "&" in event or "∩" in event:
|
|
100
|
+
result = p_a * p_b
|
|
101
|
+
|
|
102
|
+
# OR / Union
|
|
103
|
+
elif "/" in event or "∪" in event:
|
|
104
|
+
result = p_a + p_b - (p_a * p_b)
|
|
105
|
+
|
|
106
|
+
# NOT / Complement
|
|
107
|
+
elif event.startswith("!") or event.startswith("¬"):
|
|
108
|
+
result = 1 - p_a
|
|
109
|
+
|
|
110
|
+
# XOR
|
|
111
|
+
elif "^" in event or "⊕" in event:
|
|
112
|
+
result = p_a + p_b - 2 * (p_a * p_b)
|
|
113
|
+
|
|
114
|
+
# Difference
|
|
115
|
+
elif "-" in event or "∖" in event:
|
|
116
|
+
result = p_a - (p_a * p_b)
|
|
117
|
+
|
|
118
|
+
# None (all fail)
|
|
119
|
+
elif event.startswith("none"):
|
|
120
|
+
# Extract all probability arguments
|
|
121
|
+
probs = [p_a, p_b]
|
|
122
|
+
result = 1
|
|
123
|
+
for p in probs:
|
|
124
|
+
result *= (1 - p)
|
|
125
|
+
|
|
126
|
+
# At least one
|
|
127
|
+
elif event.startswith("atleast1"):
|
|
128
|
+
probs = [p_a, p_b]
|
|
129
|
+
p_none = 1
|
|
130
|
+
for p in probs:
|
|
131
|
+
p_none *= (1 - p)
|
|
132
|
+
result = 1 - p_none
|
|
133
|
+
|
|
134
|
+
else:
|
|
135
|
+
raise ValueError(f"Unknown event syntax: {event}")
|
|
136
|
+
|
|
137
|
+
return from_prob(result)
|
|
138
|
+
|
|
139
|
+
#To do:
|
|
140
|
+
# - Improve code to handle very low probabilities
|
|
141
|
+
# - Add support for more than 2 events (e.g. A, B, C, ...)
|
|
142
|
+
# - Add a function for Bayesian updating P(A|B) = P(B|A)*P(A)/P(B)
|
|
143
|
+
# - Multi-event combinations like: exactly k of n events occur (independent Bernoulli), or at least k of n occur
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Docstring for the series module
|
|
3
|
+
|
|
4
|
+
This module provides functionality to calculate series probabilities and outcomes.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
def at_least_k_of_n(k, n, p, output='result'):
|
|
10
|
+
"""Calculate the probability of getting at least k successes in n independent Bernoulli trials with success probability p.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
-----------
|
|
14
|
+
k : int
|
|
15
|
+
Minimum number of successes
|
|
16
|
+
n : int
|
|
17
|
+
Total number of trials
|
|
18
|
+
p : float
|
|
19
|
+
Probability of success on each trial (0-1). Input can also be a fraction (e.g. 1/3)
|
|
20
|
+
output : str, optional
|
|
21
|
+
Output format: 'result' (default, numeric) or 'verbose' (string)
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
if k > n:
|
|
25
|
+
return 0.0
|
|
26
|
+
if k <= 0:
|
|
27
|
+
return 1.0
|
|
28
|
+
# Calculate the cumulative probability of getting fewer than k successes
|
|
29
|
+
cumulative_prob = sum(math.comb(n, i) * (p ** i) * ((1 - p) ** (n - i)) for i in range(k))
|
|
30
|
+
if output == 'verbose':
|
|
31
|
+
print(f"The probability of getting {k} or more successes after {n} attempts/trials (each having a {p*100:.2f}% probability of success) is:")
|
|
32
|
+
return f"{(1 - cumulative_prob)*100:.2f}%"
|
|
33
|
+
else:
|
|
34
|
+
return 1 - cumulative_prob
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def gambler_ruin(i, n, p=0.5):
|
|
38
|
+
"""Calculate the probability of ending up with n after starting at i, derived from the
|
|
39
|
+
gambler's ruin Markov chain, also used for the random walk problem.
|
|
40
|
+
|
|
41
|
+
Parameters:
|
|
42
|
+
-----------
|
|
43
|
+
i : int
|
|
44
|
+
Starting amount
|
|
45
|
+
n : int
|
|
46
|
+
Target amount
|
|
47
|
+
p : float, optional
|
|
48
|
+
Probability of winning each bet or getting closer to the target (default is 0.5)
|
|
49
|
+
Returns:
|
|
50
|
+
--------
|
|
51
|
+
float
|
|
52
|
+
Probability of reaching n without first going broke (reaching 0)
|
|
53
|
+
"""
|
|
54
|
+
if i <= 0:
|
|
55
|
+
return 0.0
|
|
56
|
+
if i >= n:
|
|
57
|
+
return 1.0
|
|
58
|
+
if p == 0.5:
|
|
59
|
+
return i / n
|
|
60
|
+
else:
|
|
61
|
+
q = 1 - p
|
|
62
|
+
return (1 - (q / p) ** i) / (1 - (q / p) ** n)
|
|
63
|
+
#Validated using https://zcalculators.com/calculator/gamblers-ruin
|