1
+ from scipy .stats import norm
2
+ import math
3
+ from scipy .stats import truncnorm
4
+
5
+ class Gaussian :
6
+ def __init__ (self , mu : float , sigma : float ):
7
+ self .mu = mu
8
+ self .sigma = sigma
9
+ self .sigma2 = sigma ** 2
10
+ # Non-centered second moment
11
+ self .ncsm = self .mu ** 2 + self .sigma2
12
+
13
+ def eval (self , x : [float | list [float ]]) -> [float | list [float ]]:
14
+ return norm .pdf (x , self .mu , self .sigma )
15
+
16
+ def sample (self , size = 1 ):
17
+ return norm .rvs (self .mu , self .sigma , size = size )
18
+
19
+ def trunc (self , a : float , b : float ):
20
+ mean = self .mu # Mean of the original Gaussian distribution
21
+ std_dev = self .sigma # Standard deviation of the original Gaussian distribution
22
+ # Create a truncated normal distribution object
23
+ trunc_norm = truncnorm ((a - mean ) / std_dev , (b - mean ) / std_dev , loc = mean , scale = std_dev )
24
+ return ( Gaussian (trunc_norm .mean (),trunc_norm .std ()) )
25
+
26
+ def __mul__ (self , other ):
27
+ if isinstance (other , (int , float )):
28
+ if other == math .inf :
29
+ return Uniform ()
30
+ else :
31
+ return Gaussian (other * self .mu , abs (other )* self .sigma )
32
+ else :
33
+ if self .sigma == 0.0 or other .sigma == 0.0 :
34
+ mu = self .mu / ((self .sigma ** 2 / other .sigma ** 2 ) + 1 ) if self .sigma == 0.0 else other .mu / ((other .sigma ** 2 / self .sigma ** 2 ) + 1 )
35
+ sigma = 0.0
36
+ else :
37
+ _tau , _pi = self .tau + other .tau , self .pi + other .pi
38
+ mu , sigma = Gaussian .mu_sigma (_tau , _pi )
39
+
40
+ if sigma == math .inf :
41
+ return Uniform ()
42
+ elif sigma == 0 :
43
+ return PointMass (mu )
44
+ return Gaussian (mu , sigma )
45
+ # OLD
46
+ if (other == 0 ):
47
+ return PointMass (0 )
48
+ if (other == 1 ):
49
+ return self
50
+ elif (isinstance (other , (int , float ))):
51
+ # source: https://math.stackexchange.com/questions/275648/multiplication-of-a-random-variable-with-constant
52
+ return Gaussian (self .mu * other , self .sigma * other )
53
+ elif (isinstance (other , PointMass )):
54
+ return PointMass (self .mu * other .mu )
55
+ elif (isinstance (other , Uniform )):
56
+ return self
57
+ elif (isinstance (other , Gaussian )):
58
+ return Gaussian .mul (self , other )
59
+ else :
60
+ raise NotImplementedError (f"Multiplying Gaussian by element of type { type (other )} is unsupported. Value of other: { other } " )
61
+ def __rmul__ (self , other ):
62
+ return self .__mul__ (other )
63
+ def __matmul__ (self , other ):
64
+ return self .__mul__ (other )
65
+ def __rmatmul__ (self , other ):
66
+ return self .__rmul__ (other )
67
+ def __truediv__ (self , other ):
68
+ if (other == 0 ):
69
+ raise ZeroDivisionError (f"Tried to divide a Gaussian by zero" )
70
+ if (other == 1 ):
71
+ return self
72
+ elif (isinstance (other , (int , float ))):
73
+ return Gaussian (self .mu / other , self .sigma / other )
74
+ elif (isinstance (other , PointMass )):
75
+ assert self .sigma2 != 0 , f"This might be a pointmass: { self } "
76
+ raise ZeroDivisionError ()
77
+ #raise NotImplementedError(f"Dividing Gaussian by element of type {type(other)} is unsupported. Value of other: {other}")
78
+ # De donde salio esto: ???
79
+ #return PointMass(other.mu/((other.sigma**2/self.sigma**2) + 1))
80
+ elif (isinstance (other , Uniform )):
81
+ return self
82
+ elif (isinstance (other , Gaussian )):
83
+ return Gaussian .div (self , other )
84
+ else :
85
+ raise NotImplementedError (f"Dividing Gaussian by element of type { type (other )} is unsupported. Value of other: { other } " )
86
+ def __add__ (self , other ):
87
+ if (other == 0 ):
88
+ return self
89
+ elif (isinstance (other , Gaussian )):
90
+ return Gaussian (self .mu + other .mu , math .sqrt (self .sigma2 + other .sigma2 ))
91
+ else :
92
+ raise NotImplementedError (f"Adding Gaussian with element of type { type (other )} is unsupported. Value of other: { other } " )
93
+ def __radd__ (self , other ):
94
+ if (other == 0 ):
95
+ return self
96
+ elif (isinstance (other , Gaussian )):
97
+ return Gaussian (self .mu + other .mu , math .sqrt (self .sigma2 + other .sigma2 ))
98
+ else :
99
+ raise NotImplementedError (f"Adding Gaussian with element of type { type (other )} is unsupported. Value of other: { other } " )
100
+ def __sub__ (self , other ):
101
+ if (other == 0 ):
102
+ return self
103
+ elif (isinstance (other , Gaussian )):
104
+ return Gaussian (self .mu - other .mu , math .sqrt (self .sigma2 + other .sigma2 ))
105
+ else :
106
+ raise NotImplementedError (f"Substracting element of type { type (other )} from Gaussian is unsupported. Value of other: { other } " )
107
+ def __repr__ (self ):
108
+ return f"N(mu={ self .mu :.4f} , var={ self .sigma2 :.4f} )"
109
+ def __format__ (self , format_spec ):
110
+ return self .__repr__ ().__format__ (format_spec )
111
+
112
+ @staticmethod
113
+ def mul (norm1 , norm2 ):
114
+ sigma2Star = (1 / norm1 .sigma2 + 1 / norm2 .sigma2 )** (- 1 ) #c*N(mu, sigma) = N(c*mu, c*sigma) #TODO: agregar al pdf
115
+ muStar = norm1 .mu / norm1 .sigma2 + norm2 .mu / norm2 .sigma2
116
+ muStar *= sigma2Star
117
+ sigma = math .sqrt (sigma2Star )
118
+ if sigma == math .inf :
119
+ return Uniform ()
120
+ elif sigma == 0 :
121
+ return PointMass (muStar )
122
+ else :
123
+ return Gaussian (muStar , sigma )
124
+ #c = Gaussian(norm2.mu, math.sqrt(norm1.sigma2+norm2.sigma2)).eval(norm1.mu) #result should be multiplied by c, but is proportional to not multiplying by it
125
+ return Gaussian (muStar , math .sqrt (sigma2Star ))
126
+
127
+ @staticmethod
128
+ def div (norm1 , norm2 ):
129
+ _tau = norm1 .tau - norm2 .tau ; _pi = norm1 .pi - norm2 .pi
130
+ mu , sigma = Gaussian .mu_sigma (_tau , _pi )
131
+ if sigma == math .inf :
132
+ return Uniform ()
133
+ elif sigma == 0 :
134
+ return PointMass (mu )
135
+ else :
136
+ return Gaussian (mu , sigma )
137
+ # OLD
138
+ sigma2Star = (1 / norm1 .sigma2 - 1 / norm2 .sigma2 )** (- 1 ) #c*N(mu, sigma) = N(c*mu, c*sigma) #TODO: agregar al pdf
139
+ muStar = norm1 .mu / norm1 .sigma2 - norm2 .mu / norm2 .sigma2
140
+ muStar *= sigma2Star
141
+ #c = Gaussian(norm2.mu, math.sqrt(norm1.sigma2+norm2.sigma2)).eval(norm1.mu) #result should be multiplied by c, but is proportional to not multiplying by it
142
+ return Gaussian (muStar , math .sqrt (sigma2Star ))
143
+
144
+ @staticmethod
145
+ def mu_sigma (tau_ ,pi_ ):
146
+ if pi_ > 0.0 :
147
+ sigma = math .sqrt (1 / pi_ )
148
+ mu = tau_ / pi_
149
+ elif pi_ + 1e-5 < 0.0 :
150
+ raise ValueError (" sigma should be greater than 0 " )
151
+ else :
152
+ sigma = math .inf
153
+ mu = 0.0
154
+ return mu , sigma
155
+
156
+ @property
157
+ def tau (self ):
158
+ if self .sigma > 0.0 :
159
+ return self .mu * (self .sigma ** - 2 )
160
+ else :
161
+ return math .inf
162
+
163
+ @property
164
+ def pi (self ):
165
+ if self .sigma > 0.0 :
166
+ return self .sigma ** - 2
167
+ else :
168
+ return math .inf
169
+
170
+ class Uniform (Gaussian ):
171
+ def __init__ (self ):
172
+ super (Uniform , self ).__init__ (0 , math .inf )
173
+ def __truediv__ (self , other ):
174
+ raise NotImplementedError ("Can't divide a Uniform (inf uncertainty) by anything bc denominator must have bigger uncertainty" ) #TODO: ahcer esto bien
175
+ def __repr__ (self ):
176
+ return f"Uniform()"
177
+
178
+ class PointMass (Gaussian ):
179
+ def __init__ (self , mu ):
180
+ super (PointMass , self ).__init__ (mu , 0 )
181
+ def __mul__ (self , other ):
182
+ if (other == 0 ):
183
+ return PointMass (0 )
184
+ elif (other == 1 ):
185
+ return self
186
+ elif (isinstance (other , (int , float ))):
187
+ # source: https://math.stackexchange.com/questions/275648/multiplication-of-a-random-variable-with-constant
188
+ return PointMass (self .mu * other )
189
+ elif (isinstance (other , PointMass )):
190
+ return PointMass (self .mu * other .mu )
191
+ elif (isinstance (other , Uniform )):
192
+ return self
193
+ elif (isinstance (other , Gaussian )):
194
+ return PointMass (self .mu / ((self .sigma ** 2 / other .sigma ** 2 ) + 1 ))
195
+ else :
196
+ raise NotImplementedError (f"Adding PointMass with element of type { type (other )} is unsupported. Value of other: { other } " )
197
+ def __truediv__ (self , other ):
198
+ if (other == 0 ):
199
+ raise ZeroDivisionError (f"Tried to divide a PointMass by zero" )
200
+ if (other == 1 ):
201
+ return self
202
+ elif (isinstance (other , (int , float ))):
203
+ return PointMass (self .mu / other )
204
+ if (isinstance (other , PointMass )):
205
+ if self .mu != other .mu :
206
+ raise ZeroDivisionError ()
207
+ return Uniform ()
208
+ elif (isinstance (other , Gaussian )):
209
+ return self
210
+ else :
211
+ raise NotImplementedError (f"Dividing PointMass by element of type { type (other )} is unsupported. Value of other: { other } " )
212
+ def __repr__ (self ):
213
+ return f"PointMass({ self .mu :.4f} )"
0 commit comments