From 2924f174a6f1d3510a52b1f86d211cb306d2150a Mon Sep 17 00:00:00 2001 From: NunoSempere Date: Wed, 19 Jun 2024 09:46:08 -0400 Subject: [PATCH] more feng shui --- f.go | 285 +++++++++++++++++++++++------------------------ pretty/pretty.go | 1 - 2 files changed, 142 insertions(+), 144 deletions(-) diff --git a/f.go b/f.go index 55d7455..35d1e2b 100644 --- a/f.go +++ b/f.go @@ -59,7 +59,6 @@ func (ln Lognormal) Samples() []float64 { func (beta Beta) Samples() []float64 { sampler := func(r sample.Src) float64 { return sample.Sample_beta(beta.a, beta.b, r) } - // return sample.Sample_parallel(sampler, N_SAMPLES) return sample.Sample_serially(sampler, N_SAMPLES) } @@ -68,7 +67,7 @@ func (fs FilledSamples) Samples() []float64 { } /* Constants */ -const GENERAL_ERR_MSG = " Operation | Variable assignment | Special\n" + +const HELP_MSG = " Operation | Variable assignment | Special\n" + " Operation: operator operand\n" + " operator: (empty) | * | / | + | -\n" + " operand: scalar | lognormal | beta | variable\n" + @@ -94,131 +93,55 @@ const NORMAL90CONFIDENCE = 1.6448536269514727 const INIT_DIST Scalar = Scalar(1) const N_SAMPLES = 100_000 -/* Pretty print for distributions */ -// Needs types +/* Printers */ func prettyPrintDist(dist Dist) { switch v := dist.(type) { case Lognormal: fmt.Printf("=> ") pretty.PrettyPrint2Floats(v.low, v.high) - case FilledSamples: - tmp_xs := make([]float64, N_SAMPLES) - copy(tmp_xs, v.xs) - sort.Slice(tmp_xs, func(i, j int) bool { - return tmp_xs[i] < tmp_xs[j] - }) - low_int := N_SAMPLES / 20 - low := tmp_xs[low_int] - high_int := N_SAMPLES * 19 / 20 - high := tmp_xs[high_int] - fmt.Printf("=> ") - pretty.PrettyPrintFloat(low) - fmt.Printf(" ") - pretty.PrettyPrintFloat(high) - fmt.Printf(" (") - pretty.PrettyPrintInt(N_SAMPLES) - fmt.Printf(" samples)\n") + fmt.Println() case Beta: fmt.Printf("=> beta ") pretty.PrettyPrint2Floats(v.a, v.b) + fmt.Println() case Scalar: fmt.Printf("=> scalar ") w := float64(v) pretty.PrettyPrintFloat(w) fmt.Println() + case FilledSamples: + sorted_xs := make([]float64, N_SAMPLES) + copy(sorted_xs, v.xs) + sort.Slice(sorted_xs, func(i, j int) bool { + return sorted_xs[i] < sorted_xs[j] + }) + + low := sorted_xs[N_SAMPLES/20] + high := sorted_xs[N_SAMPLES*19/20] + fmt.Printf("=> ") + pretty.PrettyPrint2Floats(low, high) + + fmt.Printf(" (") + pretty.PrettyPrintInt(N_SAMPLES) + fmt.Printf(" samples)") + fmt.Println() default: - fmt.Printf("%v", v) + fmt.Printf("%v\n", v) } } -// Parse line into Distribution -func parseLineErr(err_msg string) (string, Dist, error) { - fmt.Println(GENERAL_ERR_MSG) +func printAndReturnErr(err_msg string) error { fmt.Println(err_msg) - var errorDist Dist - return "", errorDist, errors.New(err_msg) -} - -func parseLineIntoOpAndDist(line string, vars map[string]Dist) (string, Dist, error) { - - words := strings.Split(strings.TrimSpace(line), " ") - op := "" - var dist Dist - - switch words[0] { - case "*", "/", "+", "-": - op = words[0] - words = words[1:] - default: - op = "*" // later, change the below to - } - - switch len(words) { - case 0: - return parseLineErr("Operator must have operand; can't operate on nothing") - case 1: - var_word, var_word_exists := vars[words[0]] - single_float, err1 := strconv.ParseFloat(words[0], 64) // abstract this away to search for K/M/B/T/etc. - switch { - case var_word_exists: - dist = var_word - case err1 == nil: - dist = Scalar(single_float) - case err1 != nil && !var_word_exists: - return parseLineErr("Trying to operate on a scalar, but scalar is neither a float nor an assigned variable") - } - case 2: - new_low, err1 := strconv.ParseFloat(words[0], 64) - new_high, err2 := strconv.ParseFloat(words[1], 64) - if err1 != nil || err2 != nil { - return parseLineErr("Trying to operate by a distribution, but distribution is not specified as two floats") - } - dist = Lognormal{low: new_low, high: new_high} - case 3: - if words[0] == "beta" || words[0] == "b" { - a, err1 := strconv.ParseFloat(words[1], 64) - b, err2 := strconv.ParseFloat(words[2], 64) - if err1 != nil || err2 != nil { - return parseLineErr("Trying to specify a beta distribution? Try beta 1 2") - } - dist = Beta{a: a, b: b} - } else { - return parseLineErr("Input not understood or not implemented yet") - } - default: - return parseLineErr("Input not understood or not implemented yet") - } - return op, dist, nil - -} - -func multiplyLogDists(l1 Lognormal, l2 Lognormal) Lognormal { - logmean1 := (math.Log(l1.high) + math.Log(l1.low)) / 2.0 - logstd1 := (math.Log(l1.high) - math.Log(l1.low)) / (2.0 * NORMAL90CONFIDENCE) - - logmean2 := (math.Log(l2.high) + math.Log(l2.low)) / 2.0 - logstd2 := (math.Log(l2.high) - math.Log(l2.low)) / (2.0 * NORMAL90CONFIDENCE) - - logmean_product := logmean1 + logmean2 - logstd_product := math.Sqrt(logstd1*logstd1 + logstd2*logstd2) - - h := logstd_product * NORMAL90CONFIDENCE - loglow := logmean_product - h - loghigh := logmean_product + h - return Lognormal{low: math.Exp(loglow), high: math.Exp(loghigh)} - -} - -func multiplyBetaDists(beta1 Beta, beta2 Beta) Beta { - return Beta{a: beta1.a + beta2.a, b: beta1.b + beta2.b} + fmt.Println(HELP_MSG) + return errors.New(err_msg) } +/* Operations */ +// Generic operations with samples func operateDistsAsSamples(dist1 Dist, dist2 Dist, op string) (Dist, error) { xs := dist1.Samples() ys := dist2.Samples() - // fmt.Printf("xs: %v\n", xs) - // fmt.Printf("ys: %v\n", ys) zs := make([]float64, N_SAMPLES) for i := 0; i < N_SAMPLES; i++ { @@ -243,6 +166,28 @@ func operateDistsAsSamples(dist1 Dist, dist2 Dist, op string) (Dist, error) { return FilledSamples{xs: zs}, nil } +// Multiplication +func multiplyLogDists(l1 Lognormal, l2 Lognormal) Lognormal { + logmean1 := (math.Log(l1.high) + math.Log(l1.low)) / 2.0 + logstd1 := (math.Log(l1.high) - math.Log(l1.low)) / (2.0 * NORMAL90CONFIDENCE) + + logmean2 := (math.Log(l2.high) + math.Log(l2.low)) / 2.0 + logstd2 := (math.Log(l2.high) - math.Log(l2.low)) / (2.0 * NORMAL90CONFIDENCE) + + logmean_product := logmean1 + logmean2 + logstd_product := math.Sqrt(logstd1*logstd1 + logstd2*logstd2) + + h := logstd_product * NORMAL90CONFIDENCE + loglow := logmean_product - h + loghigh := logmean_product + h + return Lognormal{low: math.Exp(loglow), high: math.Exp(loghigh)} + +} + +func multiplyBetaDists(beta1 Beta, beta2 Beta) Beta { + return Beta{a: beta1.a + beta2.a, b: beta1.b + beta2.b} +} + func multiplyDists(old_dist Dist, new_dist Dist) (Dist, error) { switch o := old_dist.(type) { @@ -291,9 +236,17 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { switch n := new_dist.(type) { case Lognormal: // to do: check division by zero + if n.high == 0 || n.low == 0 { + fmt.Println("Error: Can't divide by 0.0") + return nil, errors.New("Error: division by zero") + } return multiplyLogDists(o, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil case Scalar: // to do: check division by zero + if n == 0.0 { + fmt.Println("Error: Can't divide by 0.0") + return nil, errors.New("Error: division by zero scalar") + } return multiplyLogDists(o, Lognormal{low: 1.0 / float64(n), high: 1.0 / float64(n)}), nil default: return operateDistsAsSamples(old_dist, new_dist, "/") @@ -306,6 +259,10 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil case Scalar: // to do: check division by zero + if n == 0.0 { + fmt.Println("Error: Can't divide by 0.0") + return nil, errors.New("Error: division by zero scalar") + } return Scalar(float64(o) / float64(n)), nil default: return operateDistsAsSamples(old_dist, new_dist, "/") @@ -316,94 +273,136 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { } } -/* Combine old dist and new line */ -// We want this as a function to be able to have parenthesis/recusion, possibly functions -func operateStackWithDist(stack Stack, new_dist Dist, op string) Stack { - - var combined_dist Dist - var err error +// Generic distribution operations +func operateDists(old_dist Dist, new_dist Dist, op string) (Dist, error) { switch op { case "*": - if combined_dist, err = multiplyDists(stack.old_dist, new_dist); err == nil { - stack.old_dist = combined_dist - } + return multiplyDists(old_dist, new_dist) case "/": - if combined_dist, err = divideDists(stack.old_dist, new_dist); err == nil { - stack.old_dist = combined_dist - } + return divideDists(old_dist, new_dist) case "+": - if combined_dist, err = operateDistsAsSamples(stack.old_dist, new_dist, "+"); err == nil { - stack.old_dist = combined_dist - } + return operateDistsAsSamples(old_dist, new_dist, "+") case "-": - if combined_dist, err = operateDistsAsSamples(stack.old_dist, new_dist, "-"); err == nil { - stack.old_dist = combined_dist + return operateDistsAsSamples(old_dist, new_dist, "-") + default: + return nil, printAndReturnErr("Can't combine distributions in this way") + } +} + +/* Parser and repl */ +func parseLineIntoOpAndDist(line string, vars map[string]Dist) (string, Dist, error) { + + words := strings.Split(strings.TrimSpace(line), " ") + op := "" + var dist Dist + + switch words[0] { + case "*", "/", "+", "-": + op = words[0] + words = words[1:] + default: + op = "*" // later, change the below to + } + + parseLineErr := func(err_msg string) (string, Dist, error) { + return "", nil, printAndReturnErr(err_msg) + } + + switch len(words) { + case 0: + return parseLineErr("Operator must have operand; can't operate on nothing") + case 1: + var_word, var_word_exists := vars[words[0]] + single_float, err1 := strconv.ParseFloat(words[0], 64) // abstract this away to search for K/M/B/T/etc. + switch { + case var_word_exists: + dist = var_word + case err1 == nil: + dist = Scalar(single_float) + case err1 != nil && !var_word_exists: + return parseLineErr("Trying to operate on a scalar, but scalar is neither a float nor an assigned variable") + } + case 2: + new_low, err1 := strconv.ParseFloat(words[0], 64) + new_high, err2 := strconv.ParseFloat(words[1], 64) + if err1 != nil || err2 != nil { + return parseLineErr("Trying to operate by a distribution, but distribution is not specified as two floats") + } + dist = Lognormal{low: new_low, high: new_high} + case 3: + if words[0] == "beta" || words[0] == "b" { + a, err1 := strconv.ParseFloat(words[1], 64) + b, err2 := strconv.ParseFloat(words[2], 64) + if err1 != nil || err2 != nil { + return parseLineErr("Trying to specify a beta distribution? Try beta 1 2") + } + dist = Beta{a: a, b: b} + } else { + return parseLineErr("Input not understood or not implemented yet") } default: - fmt.Println("Can't combine distributions in this way") + return parseLineErr("Input not understood or not implemented yet") } - return stack + return op, dist, nil } -func runRepl(stack Stack, reader *bufio.Reader) Stack { +/* Combine old dist and new line */ +// We want this as a function (rather than just be in main) +// to be able to have parenthesis/recusion, possibly functions +func runRepl(stack Stack, reader *bufio.Reader) Stack { replForLoop: for { new_line, _ := reader.ReadString('\n') words := strings.Split(strings.TrimSpace(new_line), " ") switch { - /* Empty line case */ - case strings.TrimSpace(new_line) == "": - continue replForLoop + case strings.TrimSpace(new_line) == "": /* Empty line case */ /* Parenthesis */ case len(words) == 2 && (words[0] == "*" || words[0] == "+" || words[0] == "-" || words[0] == "/") && words[1] == "(": - new_stack := runRepl(Stack{old_dist: INIT_DIST, vars: stack.vars}, reader) // to do: think if I want to shadow variables or not (right now, variables persist, since I'm copying the map(. - stack = operateStackWithDist(stack, new_stack.old_dist, words[0]) - prettyPrintDist(stack.old_dist) + new_stack := runRepl(Stack{old_dist: INIT_DIST, vars: stack.vars}, reader) + combined_dist, err := operateDists(stack.old_dist, new_stack.old_dist, words[0]) + if err == nil { + stack.old_dist = combined_dist + } case len(words) == 1 && words[0] == ")": return stack /* Special operations */ case words[0] == "exit" || words[0] == "e": os.Exit(0) case words[0] == "help" || words[0] == "h": - fmt.Println(GENERAL_ERR_MSG) - continue replForLoop + fmt.Println(HELP_MSG) case words[0] == "debug" || words[0] == "d": - fmt.Printf("Old dist: %v\n", stack.old_dist) - fmt.Printf("Vars: %v\n", stack.vars) - continue replForLoop + fmt.Printf("%v", stack) case words[0] == "clear" || words[0] == "c" || words[0] == ".": stack.old_dist = INIT_DIST fmt.Println() - continue replForLoop /* Variable assignment */ case words[0] == "=:" && len(words) == 2: stack.vars[words[1]] = stack.old_dist fmt.Printf("%s ", words[1]) - prettyPrintDist(stack.old_dist) - continue replForLoop case words[0] == "=." && len(words) == 2: stack.vars[words[1]] = stack.old_dist fmt.Printf("%s ", words[1]) prettyPrintDist(stack.old_dist) stack.old_dist = INIT_DIST - fmt.Println() - continue replForLoop + // fmt.Println() + // continue replForLoop default: op, new_dist, err := parseLineIntoOpAndDist(new_line, stack.vars) if err != nil { continue replForLoop } - stack = operateStackWithDist(stack, new_dist, op) - prettyPrintDist(stack.old_dist) + combined_dist, err := operateDists(stack.old_dist, new_dist, op) + if err == nil { + stack.old_dist = combined_dist + } } + prettyPrintDist(stack.old_dist) } } -/* Main event loop */ func main() { - reader := bufio.NewReader(os.Stdin) stack := Stack{old_dist: INIT_DIST, vars: make(map[string]Dist)} runRepl(stack, reader) diff --git a/pretty/pretty.go b/pretty/pretty.go index 371f9b4..be74d12 100644 --- a/pretty/pretty.go +++ b/pretty/pretty.go @@ -48,5 +48,4 @@ func PrettyPrint2Floats(low float64, high float64) { PrettyPrintFloat(low) fmt.Printf(" ") PrettyPrintFloat(high) - fmt.Printf("\n") }