Compare commits

..

7 Commits

Author SHA1 Message Date
84bdfa004f add some guards for edge cases
scalar = 0 * logdist
scalar < 1 * logdist
logdist with parameters < 0
logdist with same parameters (should be scalar)
logdist with low > high
2024-11-20 11:54:31 +00:00
ae1e1bbe97 Revert "tweak parsing code."
This reverts commit 6c98249e57.
2024-11-20 11:20:21 +00:00
6c98249e57 tweak parsing code. 2024-11-20 11:19:10 +00:00
4b21612d55 add todo bug found when talking with Jorge 2024-11-19 19:43:45 +00:00
c3f03d97b0 document suffixes 2024-11-10 17:16:49 +00:00
05cbc2029d add percentages! 2024-11-10 17:13:01 +00:00
27f9932db8 sets echo on by default with read from file flag 2024-11-10 17:07:19 +00:00
4 changed files with 39 additions and 10 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. 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.
## Installation ## Installation
@ -62,6 +62,7 @@ $ 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
@ -94,7 +95,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 Specifies a file with a model to run. Sets the echo command to true by default.
-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
@ -191,15 +192,20 @@ 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

Binary file not shown.

View File

@ -254,6 +254,16 @@ 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) {
@ -263,17 +273,20 @@ 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 multiplyLogDists(o, Lognormal{low: float64(n), high: float64(n)}), nil return multiplyLogDistAndScalar(o, n)
} }
} }
case Scalar: case Scalar:
{ {
if o == 1 { switch o {
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 multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, n), nil return multiplyLogDistAndScalar(n, o)
case Scalar: case Scalar:
return Scalar(float64(o) * float64(n)), nil return Scalar(float64(o) * float64(n)), nil
} }
@ -304,14 +317,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 multiplyLogDists(o, Lognormal{low: 1.0 / float64(n), high: 1.0 / float64(n)}), nil return multiplyLogDistAndScalar(o, Scalar(1.0/n))
} }
} }
case Scalar: case Scalar:
{ {
switch n := new_dist.(type) { switch n := new_dist.(type) {
case Lognormal: case Lognormal:
return multiplyLogDists(Lognormal{low: float64(o), high: float64(o)}, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil return multiplyLogDistAndScalar(Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}, o)
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")
@ -374,8 +387,15 @@ 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])
if err1 != nil || err2 != nil { switch {
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:
@ -404,7 +424,7 @@ replForLoop:
for { for {
new_line, _ := reader.ReadString('\n') new_line, _ := reader.ReadString('\n')
if *echo_flag { if *echo_flag {
fmt.Printf(new_line) fmt.Print(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)
@ -459,7 +479,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") filename := flag.String("f", "", "Specifies a file with a model to run. Sets the echo flag to true")
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()
@ -472,6 +492,7 @@ 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,6 +72,8 @@ 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':