Compare commits

..

No commits in common. "84bdfa004f946fe0aa2246a2b54ab18e274c5a5b" and "a99934387cde5a317fd8d3f0db1aa9a773143a5f" have entirely different histories.

4 changed files with 10 additions and 39 deletions

View File

@ -4,7 +4,7 @@ This project is a minimalist, calculator-style DSL for fermi estimation. It can
## Motivation ## Motivation
Sometimes, [Squiggle](https://github.com/quantified-uncertainty/squiggle), [simple squiggle](https://git.nunosempere.com/quantified.uncertainty/simple-squiggle) or [squiggle.c](https://git.nunosempere.com/personal/squiggle.c) are still too complicated and un-unix-like. In particular, their startup cost is not instant. Sometimes, [Squiggle](https://github.com/quantified-uncertainty/squiggle), [simple squiggle](https://git.nunosempere.com/quantified.uncertainty/simple-squiggle) or [squiggle.c](https://git.nunosempere.com/personal/squiggle.c) are still too complicated and un-unix-like.
## Installation ## Installation
@ -62,7 +62,6 @@ $ fermi
beta: beta alpha beta beta: beta alpha beta
Variable assignment: =: variable_name Variable assignment: =: variable_name
Variable assignment and clear stack: =. variable_name Variable assignment and clear stack: =. variable_name
Suffixes: %, K, M, B, T
Special commands: Special commands:
Comment: # this is a comment Comment: # this is a comment
Summary stats: stats Summary stats: stats
@ -95,7 +94,7 @@ Command flags:
-echo -echo
Specifies whether inputs should be echoed back. Useful if reading from a file Specifies whether inputs should be echoed back. Useful if reading from a file
. -f string . -f string
Specifies a file with a model to run. Sets the echo command to true by default. Specifies a file with a model to run
-n int -n int
Specifies the number of samples to draw when using samples (default 100000) Specifies the number of samples to draw when using samples (default 100000)
-h Shows help message -h Shows help message
@ -192,20 +191,15 @@ Done:
- [x] Figure out how to make models executable, by adding a #!/bin/bash-style command at the top? - [x] Figure out how to make models executable, by adding a #!/bin/bash-style command at the top?
- [x] Make -n flag work - [x] Make -n flag work
- [x] Add flag to repeat input lines (useful when reading from files) - [x] Add flag to repeat input lines (useful when reading from files)
- [x] Add percentages
To (possibly) do: To (possibly) do:
- [ ] Fix lognormal multiplication and division by 0 or < 0
- [ ] Consider adding an understanding of percentages
- [ ] With the -f command line option, the program doesn't read from stdin after finishing reading the file
- [ ] Add functions. Now easier to do with an explicit representation of the stakc - [ ] Add functions. Now easier to do with an explicit representation of the stakc
- [ ] Think about how to draw a histogram from samples - [ ] Think about how to draw a histogram from samples
- [ ] Dump samples to file - [ ] Dump samples to file
- [ ] Represent samples/statistics in some other way - [ ] Represent samples/statistics in some other way
- [ ] Perhaps use qsort rather than full sorting - [ ] Perhaps use qsort rather than full sorting
- [ ] Program into a small device, like a calculator? - [ ] Program into a small device, like a calculator?
- [ ] Units?
Discarded: Discarded:

BIN
fermi Executable file

Binary file not shown.

View File

@ -254,16 +254,6 @@ func multiplyBetaDists(beta1 Beta, beta2 Beta) Beta {
return Beta{a: beta1.a + beta2.a, b: beta1.b + beta2.b} return Beta{a: beta1.a + beta2.a, b: beta1.b + beta2.b}
} }
func multiplyLogDistAndScalar(l Lognormal, s Scalar) (Dist, error) {
if s == 0.0 {
return Scalar(0.0), nil
} else if s < 0.0 {
return operateDistsAsSamples(s, l, "+")
} else {
return multiplyLogDists(l, Lognormal{low: float64(s), high: float64(s)}), nil
}
}
func multiplyDists(old_dist Dist, new_dist Dist) (Dist, error) { func multiplyDists(old_dist Dist, new_dist Dist) (Dist, error) {
switch o := old_dist.(type) { switch o := old_dist.(type) {
@ -273,20 +263,17 @@ func multiplyDists(old_dist Dist, new_dist Dist) (Dist, error) {
case Lognormal: case Lognormal:
return multiplyLogDists(o, n), nil return multiplyLogDists(o, n), nil
case Scalar: case Scalar:
return multiplyLogDistAndScalar(o, n) return multiplyLogDists(o, Lognormal{low: float64(n), high: float64(n)}), nil
} }
} }
case Scalar: case Scalar:
{ {
switch o { if o == 1 {
case 1.0:
return new_dist, nil return new_dist, nil
case 0.0:
return Scalar(0.0), nil
} }
switch n := new_dist.(type) { switch n := new_dist.(type) {
case Lognormal: case Lognormal:
return multiplyLogDistAndScalar(n, o) return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, n), nil
case Scalar: case Scalar:
return Scalar(float64(o) * float64(n)), nil return Scalar(float64(o) * float64(n)), nil
} }
@ -317,14 +304,14 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) {
fmt.Println("Error: Can't divide by 0.0") fmt.Println("Error: Can't divide by 0.0")
return nil, errors.New("Error: division by zero scalar") return nil, errors.New("Error: division by zero scalar")
} }
return multiplyLogDistAndScalar(o, Scalar(1.0/n)) return multiplyLogDists(o, Lognormal{low: 1.0 / float64(n), high: 1.0 / float64(n)}), nil
} }
} }
case Scalar: case Scalar:
{ {
switch n := new_dist.(type) { switch n := new_dist.(type) {
case Lognormal: case Lognormal:
return multiplyLogDistAndScalar(Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}, o) return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil
case Scalar: case Scalar:
if n == 0.0 { if n == 0.0 {
fmt.Println("Error: Can't divide by 0.0") fmt.Println("Error: Can't divide by 0.0")
@ -387,15 +374,8 @@ func parseWordsIntoOpAndDist(words []string, vars map[string]Dist) (string, Dist
case 2: case 2:
new_low, err1 := pretty.ParseFloat(words[0]) new_low, err1 := pretty.ParseFloat(words[0])
new_high, err2 := pretty.ParseFloat(words[1]) new_high, err2 := pretty.ParseFloat(words[1])
switch { if err1 != nil || err2 != nil {
case err1 != nil || err2 != nil:
return parseWordsErr("Trying to operate by a distribution, but distribution is not specified as two floats") return parseWordsErr("Trying to operate by a distribution, but distribution is not specified as two floats")
case new_low <= 0.0 || new_high <= 0.0:
return parseWordsErr("Trying to parse two floats as a lognormal, but the two floats must be greater than 0")
case new_low == new_high:
return parseWordsErr("Trying to parse two floats as a lognormal, but the two floats must be different. Try a single scalar instead?")
case new_low > new_high:
return parseWordsErr("Trying to parse two floats as a lognormal, but the first number is larger than the second number")
} }
dist = Lognormal{low: new_low, high: new_high} dist = Lognormal{low: new_low, high: new_high}
case 3: case 3:
@ -424,7 +404,7 @@ replForLoop:
for { for {
new_line, _ := reader.ReadString('\n') new_line, _ := reader.ReadString('\n')
if *echo_flag { if *echo_flag {
fmt.Print(new_line) fmt.Printf(new_line)
} }
new_line_before_comments, _, _ := strings.Cut(new_line, "#") new_line_before_comments, _, _ := strings.Cut(new_line, "#")
new_line_trimmed := strings.TrimSpace(new_line_before_comments) new_line_trimmed := strings.TrimSpace(new_line_before_comments)
@ -479,7 +459,7 @@ replForLoop:
func main() { func main() {
num_samples_flag := flag.Int("n", N_SAMPLES, "Specifies the number of samples to draw when using samples") num_samples_flag := flag.Int("n", N_SAMPLES, "Specifies the number of samples to draw when using samples")
filename := flag.String("f", "", "Specifies a file with a model to run. Sets the echo flag to true") filename := flag.String("f", "", "Specifies a file with a model to run")
echo_flag := flag.Bool("echo", false, "Specifies whether inputs should be echoed back. Useful if reading from a file.") echo_flag := flag.Bool("echo", false, "Specifies whether inputs should be echoed back. Useful if reading from a file.")
help_flag := flag.Bool("h", false, "Shows help message") help_flag := flag.Bool("h", false, "Shows help message")
flag.Parse() flag.Parse()
@ -492,7 +472,6 @@ func main() {
if *filename != "" { if *filename != "" {
file, err := os.Open(*filename) file, err := os.Open(*filename)
if err == nil { if err == nil {
*echo_flag = true
reader = bufio.NewReader(file) reader = bufio.NewReader(file)
} else { } else {
fmt.Printf("Error opening filename; reading from stdin instead\n") fmt.Printf("Error opening filename; reading from stdin instead\n")

View File

@ -72,8 +72,6 @@ func ParseFloat(word string) (float64, error) {
n := len(word) - 1 n := len(word) - 1
f, err := strconv.ParseFloat(word[:n], 64) f, err := strconv.ParseFloat(word[:n], 64)
switch word[n] { switch word[n] {
case '%':
return multiplyOrPassThroughError(0.01, f, err)
case 'K': case 'K':
return multiplyOrPassThroughError(1_000, f, err) return multiplyOrPassThroughError(1_000, f, err)
case 'M': case 'M':