Compare commits

...

3 Commits

Author SHA1 Message Date
58f6e73d3f fengshui: README tweaks 2024-12-30 23:19:21 +01:00
6c88fbf2ec fengshui: improve README 2024-12-30 23:17:39 +01:00
8720530d4e fengshui 2024-12-29 01:04:22 +01:00
3 changed files with 153 additions and 30 deletions

View File

@ -1,4 +1,4 @@
# A calculator for distributions, for Fermi estimation
# A terminal calculator for distributions, for Fermi estimation
This project is a minimalist, calculator-style DSL for fermi estimation. It can multiply, divide, add and subtract scalars, lognormals and beta distributions, and supports variables.
@ -6,36 +6,22 @@ This project is a minimalist, calculator-style DSL for fermi estimation. It can
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
## Build instructions
Install the [go toolchain](https://go.dev/dl/), then:
```
git clone https://git.nunosempere.com/NunoSempere/fermi
cd fermi
make build
sudo make install
fermi
./fermi
# sudo make install
# fermi
```
## Usage
```
$ fermi
5000000 12000000
=> 5.0M 12.0M
* beta 1 200
=> 1.9K 123.1K
* 30 180
=> 122.9K 11.7M
/ 48 52
=> 2.5K 234.6K
/ 5 6
=> 448.8 43.0K
/ 6 8
=> 64.5 6.2K
/ 60
=> 1.1 103.7
```
Perhaps this example is more understandable with comments and better units:
```
$ fermi
5M 12M # number of people living in Chicago
@ -48,10 +34,12 @@ beta 1 200 # fraction of people that have a piano
=: piano_tuners
```
Here are some real-life examples: [Chance for a Russian male of fighting age of being drafted](https://x.com/NunoSempere/status/1829525844169248912), [did the startup Friend burn too much cash](https://x.com/NunoSempere/status/1818810770932568308), [how much did Nikita Bier make mentoring?](https://x.com/NunoSempere/status/1815169781907042504), [what fraction of North Korea's caloric intake is Russia supporting?](https://x.com/NunoSempere/status/1855666428835140078). In general, as a terminal guy, I've found that having zero startup cost makes creating small fermi models much cheaper, and thus happen more often.
If you type "help" (or run fermi -h), you can see a small grammar and some optional command flags:
```
$ fermi
$ fermi -h
1. Grammar:
Operation | Variable assignment | Special
@ -88,6 +76,7 @@ $ fermi
1 10
+ beta 1 100
)
/ 1%
=. y
mx x 1 y 2.33
+ mx x 30% y 70%
@ -104,12 +93,11 @@ $ fermi
```
You can see real life examples [here](https://x.com/NunoSempere/status/1831106442721452312), [here](https://x.com/NunoSempere/status/1829525844169248912), [here](https://x.com/NunoSempere/status/1818810770932568308), [here](https://x.com/NunoSempere/status/1816605190415401100), [here](https://x.com/NunoSempere/status/1816604386703081894), [here](https://x.com/NunoSempere/status/1815169781907042504)
## Tips & tricks
- It's conceptually clearer to have all the multiplications first and then all the divisions
- For distributions between 0 and 1, consider using a beta distribution
- The default operation is multiplication
### Command line options
@ -197,11 +185,11 @@ Done:
- [x] Add percentages
- [x] Consider adding an understanding of percentages
- [x] Improve and rationalize error messages a bit
- [x] Add, then document mixture distributions
To (possibly) do:
- [ ] Consider implications of sampling strategy for operating variables in this case.
- [ ] Document mixture distributions
- [ ] Consider implications of sampling strategy for operating variables.
- [ ] Fix lognormal multiplication and division by 0 or < 0
- [ ] 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 stack

View File

@ -2,7 +2,7 @@ build:
go build main/fermi.go main/pretty.go main/error.go
run:
go run fermi.go
go run main/fermi.go main/error.go main/pretty.go
install: fermi
rm /usr/bin/fermi

135
simple/simple_pretty.go Normal file
View File

@ -0,0 +1,135 @@
package main
import (
"errors"
"fmt"
"git.nunosempere.com/NunoSempere/fermi/sample"
"math"
"sort"
"strconv"
)
func PrettyPrintInt(n int) {
switch {
case math.Abs(float64(n)) >= 1_000_000_000_000:
fmt.Printf("%.2fT", float64(n)/1_000_000_000_000.0)
case math.Abs(float64(n)) >= 1_000_000_000:
fmt.Printf("%.2fB", float64(n)/1_000_000_000.0)
case math.Abs(float64(n)) >= 1_000_000:
fmt.Printf("%.2fM", float64(n)/1_000_000.0)
case math.Abs(float64(n)) >= 1_000:
fmt.Printf("%.2fK", float64(n)/1_000.0)
default:
fmt.Printf("%d", n)
}
}
func PrettyPrintFloat(f float64) {
switch {
case math.Abs(f) >= 1_000_000_000_000:
fmt.Printf("%.2fT", f/1_000_000_000_000)
case math.Abs(f) >= 1_000_000_000:
fmt.Printf("%.2fB", f/1_000_000_000)
case math.Abs(f) >= 1_000_000:
fmt.Printf("%.2fM", f/1_000_000)
case math.Abs(f) >= 1_000:
fmt.Printf("%.2fK", f/1_000)
case math.Abs(f) <= 0.0001:
fmt.Printf("%.6f", f)
case math.Abs(f) <= 0.001:
fmt.Printf("%.5f", f)
case math.Abs(f) <= 0.01:
fmt.Printf("%.4f", f)
case math.Abs(f) <= 0.1:
fmt.Printf("%.3f", f)
default:
fmt.Printf("%.2f", f)
}
}
func PrettyPrint2Floats(low float64, high float64) {
PrettyPrintFloat(low)
fmt.Printf(" ")
PrettyPrintFloat(high)
}
func multiplyOrPassThroughError(a float64, b float64, err error) (float64, error) {
if err != nil {
return b, err
} else {
return a * b, nil
}
}
func ParseFloat(word string) (float64, error) {
// l = len(word) // assuming no UTF stuff
switch len(word) {
case 0:
return 0, errors.New("String to be parsed into float must not be the empty string")
case 1:
return strconv.ParseFloat(word, 64)
}
n := len(word) - 1
f, err := strconv.ParseFloat(word[:n], 64)
switch word[n] {
case '%':
return multiplyOrPassThroughError(0.01, f, err)
case 'K':
return multiplyOrPassThroughError(1_000, f, err)
case 'M':
return multiplyOrPassThroughError(1_000_000, f, err)
case 'B':
return multiplyOrPassThroughError(1_000_000_000, f, err)
case 'T':
return multiplyOrPassThroughError(1_000_000_000_000, f, err)
default:
return strconv.ParseFloat(word, 64)
}
}
/* Printers */
func PrettyPrintDist(dist Dist) {
switch v := dist.(type) {
case Lognormal:
fmt.Printf("=> ")
PrettyPrint2Floats(v.low, v.high)
fmt.Println()
case Beta:
fmt.Printf("=> beta ")
PrettyPrint2Floats(v.a, v.b)
fmt.Println()
case Scalar:
fmt.Printf("=> scalar ")
w := float64(v)
PrettyPrintFloat(w)
fmt.Println()
case FilledSamples:
n := len(v.xs)
sorted_xs := make([]float64, n)
copy(sorted_xs, v.xs)
sort.Slice(sorted_xs, func(i, j int) bool {
return sorted_xs[i] < sorted_xs[j]
})
low := sorted_xs[int(math.Round(float64(n)*0.05))]
high := sorted_xs[int(math.Round(float64(n)*0.95))]
fmt.Printf("=> ")
PrettyPrint2Floats(low, high)
fmt.Printf(" (")
PrettyPrintInt(N_SAMPLES)
fmt.Printf(" samples)")
fmt.Println()
default:
fmt.Printf("%v\n", v)
}
}
func PrintAndReturnErr(err_msg string) error {
fmt.Println(err_msg)
fmt.Println("Type \"help\" (without quotes) to see a pseudogrammar and examples")
return errors.New(err_msg)
}