f_simple.go (9602B)
1 package main 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "git.nunosempere.com/NunoSempere/fermi/pretty" 8 "git.nunosempere.com/NunoSempere/fermi/sample" 9 "math" 10 "os" 11 "sort" 12 "strconv" 13 "strings" 14 ) 15 16 /* Types and interfaces */ 17 18 type Dist interface { 19 Samples() []float64 20 } 21 22 type Scalar float64 23 24 type Lognormal struct { 25 low float64 26 high float64 27 } 28 29 type Beta struct { 30 a float64 31 b float64 32 } 33 34 type FilledSamples struct { 35 xs []float64 36 } 37 38 /* Dist interface functions */ 39 // https://go.dev/tour/methods/9 40 func (p Scalar) Samples() []float64 { 41 xs := make([]float64, N_SAMPLES) 42 for i := 0; i < N_SAMPLES; i++ { 43 xs[i] = float64(p) 44 } 45 return xs 46 } 47 48 func (ln Lognormal) Samples() []float64 { 49 sampler := func(r sample.Src) float64 { return sample.Sample_to(ln.low, ln.high, r) } 50 return sample.Sample_serially(sampler, N_SAMPLES) 51 } 52 53 func (beta Beta) Samples() []float64 { 54 sampler := func(r sample.Src) float64 { return sample.Sample_beta(beta.a, beta.b, r) } 55 return sample.Sample_serially(sampler, N_SAMPLES) 56 } 57 58 func (fs FilledSamples) Samples() []float64 { 59 return fs.xs 60 } 61 62 /* Constants */ 63 const HELP_MSG = " Operation | Variable assignment | Special\n" + 64 " Operation: operator operand\n" + 65 " operator: (empty) | * | / | + | -\n" + 66 " operand: scalar | lognormal | beta\n" + 67 " lognormal: low high\n" + 68 " beta: beta alpha beta\n" + 69 " Clear stack: . | c | clear\n" + 70 " Other special operations: help | debug | exit\n" + 71 " Examples: \n" + 72 " + 2\n" + 73 " / 2.5\n" + 74 " * 1 10 (interpreted as lognormal)\n" + 75 " + 1 10\n" + 76 " * beta 1 10\n" + 77 " 1 10 (multiplication taken as default operation)\n" + 78 " .\n" + 79 " 1 100\n" + 80 " exit\n" 81 const NORMAL90CONFIDENCE = 1.6448536269514727 82 const INIT_DIST Scalar = Scalar(1) 83 const N_SAMPLES = 100_000 84 85 /* Printers */ 86 func prettyPrintDist(dist Dist) { 87 switch v := dist.(type) { 88 case Lognormal: 89 fmt.Printf("=> ") 90 pretty.PrettyPrint2Floats(v.low, v.high) 91 fmt.Println() 92 case Beta: 93 fmt.Printf("=> beta ") 94 pretty.PrettyPrint2Floats(v.a, v.b) 95 fmt.Println() 96 case Scalar: 97 fmt.Printf("=> scalar ") 98 w := float64(v) 99 pretty.PrettyPrintFloat(w) 100 fmt.Println() 101 case FilledSamples: 102 sorted_xs := make([]float64, N_SAMPLES) 103 copy(sorted_xs, v.xs) 104 sort.Slice(sorted_xs, func(i, j int) bool { 105 return sorted_xs[i] < sorted_xs[j] 106 }) 107 108 low := sorted_xs[N_SAMPLES/20] 109 high := sorted_xs[N_SAMPLES*19/20] 110 fmt.Printf("=> ") 111 pretty.PrettyPrint2Floats(low, high) 112 113 fmt.Printf(" (") 114 pretty.PrettyPrintInt(N_SAMPLES) 115 fmt.Printf(" samples)") 116 fmt.Println() 117 default: 118 fmt.Printf("%v\n", v) 119 } 120 } 121 122 func printAndReturnErr(err_msg string) error { 123 fmt.Println(err_msg) 124 fmt.Println(HELP_MSG) 125 return errors.New(err_msg) 126 } 127 128 /* Operations */ 129 // Generic operations with samples 130 func operateDistsAsSamples(dist1 Dist, dist2 Dist, op string) (Dist, error) { 131 132 xs := dist1.Samples() 133 ys := dist2.Samples() 134 zs := make([]float64, N_SAMPLES) 135 136 for i := 0; i < N_SAMPLES; i++ { 137 switch op { 138 case "*": 139 zs[i] = xs[i] * ys[i] 140 case "/": 141 if ys[0] != 0 { 142 zs[i] = xs[i] / ys[i] 143 } else { 144 fmt.Println("Error: When dividing as samples, division by zero") 145 return nil, errors.New("Division by zero") 146 } 147 case "+": 148 zs[i] = xs[i] + ys[i] 149 case "-": 150 zs[i] = xs[i] - ys[i] 151 } 152 } 153 154 // fmt.Printf("%v\n", zs) 155 return FilledSamples{xs: zs}, nil 156 } 157 158 // Multiplication 159 func multiplyLogDists(l1 Lognormal, l2 Lognormal) Lognormal { 160 logmean1 := (math.Log(l1.high) + math.Log(l1.low)) / 2.0 161 logstd1 := (math.Log(l1.high) - math.Log(l1.low)) / (2.0 * NORMAL90CONFIDENCE) 162 163 logmean2 := (math.Log(l2.high) + math.Log(l2.low)) / 2.0 164 logstd2 := (math.Log(l2.high) - math.Log(l2.low)) / (2.0 * NORMAL90CONFIDENCE) 165 166 logmean_product := logmean1 + logmean2 167 logstd_product := math.Sqrt(logstd1*logstd1 + logstd2*logstd2) 168 169 h := logstd_product * NORMAL90CONFIDENCE 170 loglow := logmean_product - h 171 loghigh := logmean_product + h 172 return Lognormal{low: math.Exp(loglow), high: math.Exp(loghigh)} 173 174 } 175 176 func multiplyBetaDists(beta1 Beta, beta2 Beta) Beta { 177 return Beta{a: beta1.a + beta2.a, b: beta1.b + beta2.b} 178 } 179 180 func multiplyDists(old_dist Dist, new_dist Dist) (Dist, error) { 181 182 switch o := old_dist.(type) { 183 case Lognormal: 184 { 185 switch n := new_dist.(type) { 186 case Lognormal: 187 return multiplyLogDists(o, n), nil 188 case Scalar: 189 return multiplyLogDists(o, Lognormal{low: float64(n), high: float64(n)}), nil 190 default: 191 return operateDistsAsSamples(old_dist, new_dist, "*") 192 } 193 } 194 case Scalar: 195 { 196 if o == 1 { 197 return new_dist, nil 198 } 199 switch n := new_dist.(type) { 200 case Lognormal: 201 return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, n), nil 202 case Scalar: 203 return Scalar(float64(o) * float64(n)), nil 204 default: 205 return operateDistsAsSamples(old_dist, new_dist, "*") 206 } 207 } 208 case Beta: 209 switch n := new_dist.(type) { 210 case Beta: 211 return multiplyBetaDists(o, n), nil 212 default: 213 return operateDistsAsSamples(old_dist, new_dist, "*") 214 } 215 default: 216 return operateDistsAsSamples(old_dist, new_dist, "*") 217 } 218 } 219 220 func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { 221 222 switch o := old_dist.(type) { 223 case Lognormal: 224 { 225 switch n := new_dist.(type) { 226 case Lognormal: 227 // to do: check division by zero 228 if n.high == 0 || n.low == 0 { 229 fmt.Println("Error: Can't divide by 0.0") 230 return nil, errors.New("Error: division by zero") 231 } 232 return multiplyLogDists(o, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil 233 case Scalar: 234 // to do: check division by zero 235 if n == 0.0 { 236 fmt.Println("Error: Can't divide by 0.0") 237 return nil, errors.New("Error: division by zero scalar") 238 } 239 return multiplyLogDists(o, Lognormal{low: 1.0 / float64(n), high: 1.0 / float64(n)}), nil 240 default: 241 return operateDistsAsSamples(old_dist, new_dist, "/") 242 } 243 } 244 case Scalar: 245 { 246 switch n := new_dist.(type) { 247 case Lognormal: 248 return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil 249 case Scalar: 250 // to do: check division by zero 251 if n == 0.0 { 252 fmt.Println("Error: Can't divide by 0.0") 253 return nil, errors.New("Error: division by zero scalar") 254 } 255 return Scalar(float64(o) / float64(n)), nil 256 default: 257 return operateDistsAsSamples(old_dist, new_dist, "/") 258 } 259 } 260 default: 261 return operateDistsAsSamples(old_dist, new_dist, "/") 262 } 263 } 264 265 // Generic distribution operations 266 func operateDists(old_dist Dist, new_dist Dist, op string) (Dist, error) { 267 switch op { 268 case "*": 269 return multiplyDists(old_dist, new_dist) 270 case "/": 271 return divideDists(old_dist, new_dist) 272 case "+": 273 return operateDistsAsSamples(old_dist, new_dist, "+") 274 case "-": 275 return operateDistsAsSamples(old_dist, new_dist, "-") 276 default: 277 return nil, printAndReturnErr("Can't combine distributions in this way") 278 } 279 } 280 281 /* Parser and repl */ 282 func parseWordsErr(err_msg string) (string, Dist, error) { 283 return "", nil, printAndReturnErr(err_msg) 284 } 285 func parseWordsIntoOpAndDist(words []string) (string, Dist, error) { 286 287 op := "" 288 var dist Dist 289 290 switch words[0] { 291 case "*", "/", "+", "-": 292 op = words[0] 293 words = words[1:] 294 default: 295 op = "*" // later, change the below to 296 } 297 298 switch len(words) { 299 case 0: 300 return parseWordsErr("Operator must have operand; can't operate on nothing") 301 case 1: 302 single_float, err := strconv.ParseFloat(words[0], 64) // abstract this away to search for K/M/B/T/etc. 303 if err != nil { 304 return parseWordsErr("Trying to operate on a scalar, but scalar is neither a float nor an assigned variable") 305 } 306 dist = Scalar(single_float) 307 case 2: 308 new_low, err1 := strconv.ParseFloat(words[0], 64) 309 new_high, err2 := strconv.ParseFloat(words[1], 64) 310 if err1 != nil || err2 != nil { 311 return parseWordsErr("Trying to operate by a distribution, but distribution is not specified as two floats") 312 } 313 dist = Lognormal{low: new_low, high: new_high} 314 case 3: 315 if words[0] == "beta" || words[0] == "b" { 316 a, err1 := strconv.ParseFloat(words[1], 64) 317 b, err2 := strconv.ParseFloat(words[2], 64) 318 if err1 != nil || err2 != nil { 319 return parseWordsErr("Trying to specify a beta distribution? Try beta 1 2") 320 } 321 dist = Beta{a: a, b: b} 322 } else { 323 return parseWordsErr("Input not understood or not implemented yet") 324 } 325 default: 326 return parseWordsErr("Input not understood or not implemented yet") 327 } 328 return op, dist, nil 329 330 } 331 332 func main() { 333 reader := bufio.NewReader(os.Stdin) 334 var old_dist Dist = INIT_DIST 335 336 replForLoop: 337 for { 338 339 new_line, _ := reader.ReadString('\n') 340 words := strings.Split(strings.TrimSpace(new_line), " ") 341 342 switch { 343 case strings.TrimSpace(new_line) == "": /* Empty line case */ 344 continue replForLoop 345 case words[0] == "exit" || words[0] == "e": 346 os.Exit(0) 347 case words[0] == "help" || words[0] == "h": 348 fmt.Println(HELP_MSG) 349 continue replForLoop 350 case words[0] == "debug" || words[0] == "d": 351 fmt.Printf("%v", old_dist) 352 continue replForLoop 353 case words[0] == "clear" || words[0] == "c" || words[0] == ".": 354 old_dist = INIT_DIST 355 fmt.Println() 356 continue replForLoop 357 } 358 359 op, new_dist, err := parseWordsIntoOpAndDist(words) 360 if err != nil { 361 continue replForLoop 362 } 363 combined_dist, err := operateDists(old_dist, new_dist, op) 364 if err != nil { 365 continue replForLoop 366 } 367 old_dist = combined_dist 368 prettyPrintDist(old_dist) 369 } 370 }