diff --git a/.gitignore b/.gitignore index ac8722849..b2857b2cc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ www/demo demo.log *.class cost +previous diff --git a/.travis.yml b/.travis.yml index f0e366563..efda69ce8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,43 +1,42 @@ language: c sudo: required -services: - - docker -cache: - directories: - - docker-images env: global: - RACKET_DIR=~/racket - matrix: - - RACKET_VERSION="6.3" - HERBIE_SEED="#(2749829514 1059579101 312104142 915324965 966790849 1349306526)" - - RACKET_VERSION="6.4" - HERBIE_SEED="#(2749829514 1059579101 312104142 915324965 966790849 1349306526)" - - RACKET_VERSION="6.5" - HERBIE_SEED="#(2749829514 1059579101 312104142 915324965 966790849 1349306526)" - - RACKET_VERSION="6.6" + TBENCHES="bench/tutorial.fpcore bench/hamming/" HERBIE_SEED="#(2749829514 1059579101 312104142 915324965 966790849 1349306526)" + TSEED="racket $TRAVIS_BUILD_DIR/infra/travis.rkt --seed '${HERBIE_SEED}' $TBENCHES" + TRAND="racket $TRAVIS_BUILD_DIR/infra/travis.rkt $TBENCHES" + UTEST="raco test src" + matrix: + # separate builds for travis benches and unit tests - RACKET_VERSION="6.7" - HERBIE_SEED="#(2749829514 1059579101 312104142 915324965 966790849 1349306526)" + JOB="${TSEED}" + - RACKET_VERSION="6.9" + JOB="${TSEED}" + - RACKET_VERSION="6.11" + JOB="${TSEED}" - RACKET_VERSION="6.7" - HERBIE_SEED="#f" + JOB="${UTEST}" + - RACKET_VERSION="6.9" + JOB="${UTEST}" + - RACKET_VERSION="6.11" + JOB="${UTEST}" + # remember to change the `allow_failures` key below! + - RACKET_VERSION="6.11" + JOB="${TRAND}" matrix: allow_failures: - - env: RACKET_VERSION="6.7" - HERBIE_SEED="#f" + - env: RACKET_VERSION="6.11" + JOB="${TRAND}" before_install: - git clone https://github.com/greghendershott/travis-racket.git ../travis-racket - cat ../travis-racket/install-racket.sh | bash - export PATH="${RACKET_DIR}/bin:${PATH}" -# - docker load -i docker-images/herbie.image || true install: - raco pkg install --auto $TRAVIS_BUILD_DIR/src -# - docker build -t herbie . script: - - raco test src - - racket $TRAVIS_BUILD_DIR/infra/travis.rkt --seed "${HERBIE_SEED}" bench/tutorial.fpcore bench/hamming/ -#before_cache: -# - docker save -o docker-images/herbie.image herbie + - echo ${JOB} && eval ${JOB} notifications: slack: secure: QB8ib/gxZWZ8rY9H54BktIgx8LfjdqabSAkmWip0VHlUhrh2ULG566XgmB5h75eNzCil2cw76ma5wfSC0MNIQ1iDHKCxAgTE0+gcPcZAYGfucQ28sKGBG2wcuJfvBLG6lVDxj+luGUh3XohouTLYI9cg509JBgTgpcrXVexYAaE= diff --git a/Dockerfile b/Dockerfile index a0aa24013..4ff1f839d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM jackfirth/racket:6.8 +FROM jackfirth/racket:6.12 MAINTAINER Pavel Panchekha RUN apt-get update \ && apt-get install -y libcairo2-dev libjpeg62 libpango1.0-dev \ diff --git a/Makefile b/Makefile index b3fdca0e6..dc77b0d3d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: report publish www compile clean loc deploy +.PHONY: all install update nightly index clean publish start-server package loc deploy all: @echo "Type 'make install' to install Herbie as a Racket package," @@ -10,6 +10,14 @@ install: update: raco pkg update --name herbie src/ +nightly: + bash infra/nightly.sh + bash infra/nightly.sh --enable rules:numerics + $(MAKE) index + +index: + bash infra/publish.sh index + herbie.zip herbie.zip.CHECKSUM: raco pkg create src/ mv src.zip herbie.zip @@ -24,10 +32,10 @@ publish: bash infra/publish.sh index start-server: - racket src/herbie.rkt web --seed '#(2775764126 3555076145 3898259844 1891440260 2599947619 1948460636)' --timeout 60 --demo --prefix /demo/ --port 4053 --save-session www/demo/ --log infra/server.log --quiet 2>&1 + racket src/herbie.rkt web --seed '#(2775764126 3555076145 3898259844 1891440260 2599947619 1948460636)' --timeout 60 --num-iters 2 --demo --prefix /demo/ --port 4053 --save-session www/demo/ --log infra/server.log --quiet 2>&1 package: - raco pkg + raco pkg loc: find herbie/ -type f -exec cat {} \; | wc -l diff --git a/README.md b/README.md index 89e9f4d9a..131a5e553 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,22 @@ ![Herbie](logo.png) -Herbie synthesizes floating-point programs from real-number programs, -automatically handling simple numerical instabilities. -Visit [our website](https://herbie.uwplse.org) for tutorials, -documentation, and an online demo. - -Current Status --------------- [![Build Status](https://travis-ci.org/uwplse/herbie.svg?branch=master)](https://travis-ci.org/uwplse/herbie) -Herbie can improve the accuracy of many real-world programs, and is -used by scientists in many disciplines. It has lead to two patches -(for -complex [square roots](https://github.com/josdejong/mathjs/pull/208) -and -[trigonometric functions](https://github.com/josdejong/mathjs/pull/247)), -in [math.js](http://mathjs.org/) an open-source mathematics library. -Herbie has semi-regular releases twice a year, maintains backwards -compatibility, and uses standardized formats. - -Helping Out ------------ - -Herbie development is organized on our -[mailing list](https://mailman.cs.washington.edu/mailman/listinfo/herbie) -where we discuss work in progress and announce major improvements. -[Email us](mailto:herbie@cs.washington.edu) to get involved! - -We use [Github](https://github.com/uwplse/herbie) -and [Trello](https://trello.com/b/lh7b33Dr/herbie) to organize some -development goals Our test results -are [archived](http://herbie.uwplse.org/reports/). +Herbie synthesizes floating-point programs from real-number programs, +automatically handling simple numerical instabilities. Visit [our +website](https://herbie.uwplse.org) for tutorials, documentation, and +an online demo. Herbie has semi-regular releases twice a year, +maintains backwards compatibility, and uses standardized formats. Installing ---------- For full details on installing Herbie, please see the -[tutorial](http://herbie.uwplse.org/doc/latest/installing-herbie.html). +[tutorial](http://herbie.uwplse.org/doc/latest/installing.html). -Herbie requires Racket 6.3 or later, and supports Linux and OS X. -Install it with: +Herbie requires Racket 6.7 or later, and supports Windows, OS X, and +Linux. Install it with: raco pkg install herbie @@ -66,9 +42,10 @@ Run Herbie from the top-level directory of the repo, and enter the cancellation test: $ herbie shell - Seed: #(1046809171 2544984934 1871826185 4237421819 4093186437 162666889) + Herbie 1.2 with seed #(349461420 3681359142 2680361770 2900531005 1939065059 1779362427) + Find help on , exit with Ctrl-D herbie> (FPCore (x) (- (+ 1 x) x)) - (FPCore (x) 1) + (FPCore (x) ... 1) The output is Herbie's improved, more-accurate expression, in this case the constant `1`. @@ -79,6 +56,18 @@ Consult the [documentation](http://herbie.uwplse.org/doc/latest/options.html). for more. +Helping Out +----------- + +Herbie development is organized on our +[mailing list](https://mailman.cs.washington.edu/mailman/listinfo/herbie) +where we discuss work in progress and announce major improvements. +[Email us](mailto:herbie@cs.washington.edu) to get involved! + +We use [Github](https://github.com/uwplse/herbie) +and [Trello](https://trello.com/b/lh7b33Dr/herbie) to organize some +development goals. + Running Tests ------------- @@ -92,17 +81,15 @@ projects, examples emailed to the developers, and from numerical analysis textbooks. This suite is found in `bench/`. The full test can be run with - herbie report bench/ + herbie report bench/ graphs/ -This full test can take several hours to run. We often test Herbie on -basic but representative examples with: +The output is an HTML report in `graphs/`. This full test can take +several hours to run. We often test Herbie on basic but representative +examples with: - herbie report bench/hamming/ + herbie report bench/hamming/ graphs/ This takes approximately 15 minutes. -Test results are collected on -[uwplse.org](http://herbie.uwplse.org/reports/). If you have an -account on this server, you can publish your test results with - - make publish +Historic and nightly test results are collected on +[uwplse.org](http://herbie.uwplse.org/reports/). diff --git a/bench/challenge/input-sampling.fpcore b/bench/challenge/input-sampling.fpcore new file mode 100644 index 000000000..1ceb03274 --- /dev/null +++ b/bench/challenge/input-sampling.fpcore @@ -0,0 +1,24 @@ +; -*- mode: scheme -*- + +; Herbie cannot sample enough input points for these tests. + +(FPCore (x) + :name "Random Jason Timeout Test 001" + (+ x (asin (cosh x)))) + +(FPCore (x y) + :name "Random Jason Timeout Test 009" + (fabs (fmod y (asin (- 2.821952756469356e+184 x))))) + +(FPCore (a b c) + :pre (and (< 0 a) (< 0 b) (< 0 c)) + :name "Area of a triangle" + (sqrt (* (* (* (/ (+ (+ a b) c) 2) (- (/ (+ (+ a b) c) 2) a)) + (- (/ (+ (+ a b) c) 2) b)) + (- (/ (+ (+ a b) c) 2) c)))) + +(FPCore (n U t l Om U*) + :name "Toniolo and Linder, Equation (13)" + (sqrt (* (* (* 2 n) U) + (- (- t (* 2 (/ (* l l) Om))) + (* (* n (pow (/ l Om) 2)) (- U U*)))))) diff --git a/bench/challenge/mpfr-performance.fpcore b/bench/challenge/mpfr-performance.fpcore new file mode 100644 index 000000000..05d4f37fd --- /dev/null +++ b/bench/challenge/mpfr-performance.fpcore @@ -0,0 +1,14 @@ +; -*- mode: scheme -*- + +; Herbie cannot evaluate these tests fast enough with MPFR. +; In contrast to challenge/timeout.fpcore, these tests do +; not finish sampling input points (sometimes varying with seed). + +(FPCore (a b) + :name "Random Jason Timeout Test 003" + (sin (pow (sqrt (atan2 b b)) (- b a)))) + +(FPCore (a b) + :name "Random Jason Timeout Test 015" + (sin (pow (sqrt (atan2 b b)) (- b a)))) + diff --git a/bench/challenge/overflow.fpcore b/bench/challenge/overflow.fpcore new file mode 100644 index 000000000..91ae994e2 --- /dev/null +++ b/bench/challenge/overflow.fpcore @@ -0,0 +1,3 @@ +(FPCore (lo hi x) + :pre (and (< lo -1e308) (> hi 1e308)) + (/ (- x lo) (- hi lo))) diff --git a/bench/challenge/precision-limits.fpcore b/bench/challenge/precision-limits.fpcore new file mode 100644 index 000000000..6c1a5f3c2 --- /dev/null +++ b/bench/challenge/precision-limits.fpcore @@ -0,0 +1,20 @@ +; -*- mode: scheme -*- + +; Herbie requires too much MPFR precision for these tests. + +(FPCore (c) + :name "Random Jason Timeout Test 002" + (fmod (sinh c) (- c (pow -2.9807307601812193e+165 2)))) + +(FPCore (a c) + :name "Random Jason Timeout Test 004" + (fmod (cosh c) (log1p a))) + +(FPCore (a) + :name "Random Jason Timeout Test 012" + (acos (pow (fmod (cosh a) (* a a)) (log1p a)))) + +(FPCore (c) + :name "Random Jason Timeout Test 014" + (fmod (sinh c) (- c (pow -2.9807307601812193e+165 2)))) + diff --git a/bench/challenge/timeout.fpcore b/bench/challenge/timeout.fpcore new file mode 100644 index 000000000..c3a1c9045 --- /dev/null +++ b/bench/challenge/timeout.fpcore @@ -0,0 +1,8 @@ +; -*- mode: scheme -*- + +; Herbie times out on these tests, but gets past sampling. + +(FPCore (a) + :name "Random Jason Timeout Test 006" + (fabs (fmod (atan2 (expm1 (sin (expm1 a))) (atan a)) a))) + diff --git a/bench/hamming/complex.fpcore b/bench/hamming/complex.fpcore new file mode 100644 index 000000000..4aa8508fa --- /dev/null +++ b/bench/hamming/complex.fpcore @@ -0,0 +1,44 @@ +; -*- mode: scheme -*- + +; TODO: exp function unimplemented. +#;(FPCore (xre xim) + :name "exp with complex power real part (p55)" + (let ([x (complex xre xim)]) + (re (/ (+ (exp x) (exp (- x))) (complex 2 0))))) + +#;(FPCore (xre xim) + :name "exp with complex power imaginary part (p55)" + (let ([x (complex xre xim)]) + (im (/ (+ (exp x) (exp (- x))) (complex 2 0))))) + +#;(FPCore (x y) + :name "Euler formula real part (p55)" + (let ([a (/ (+ (exp x) (exp (- x))) 2)] + [b (/ (- (exp x) (exp (- x))) 2)]) + (re (complex (* a (cos y)) (* b (sin y)))))) + +#;(FPCore (x y) + :name "Euler formula imaginary part (p55)" + (let ([a (/ (+ (exp x) (exp (- x))) 2)] + [b (/ (- (exp x) (exp (- x))) 2)]) + (im (complex (* a (cos y)) (* b (sin y)))))) + +(FPCore () + :name "3.9.1 real part (p56)" + (let ([x (complex -1 1)]) + (re (+ (* x x x x x x) (* (complex 6 0) x x x x x) (* (complex 15 0) x x x x) (* (complex 20 0) x x x) (* (complex 15 0) x x) (* (complex 6 0) x) (complex 1 0))))) + +(FPCore () + :name "3.9.1 imaginary part (p56)" + (let ([x (complex -1 1)]) + (im (+ (* x x x x x x) (* (complex 6 0) x x x x x) (* (complex 15 0) x x x x) (* (complex 20 0) x x x) (* (complex 15 0) x x) (* (complex 6 0) x) (complex 1 0))))) + +(FPCore () + :name "3.9.2 real part (p56)" + (let ([x (complex (/ (- 1) 2) (/ (sqrt 3) 2))]) + (re (+ (* x x x x) (* (complex (- 2) 0) x x x) (* (complex 5 0) x x) (* (complex 4 0) x) (complex 7 0))))) + +(FPCore () + :name "3.9.2 imaginary part (p56)" + (let ([x (complex (/ (- 1) 2) (/ (sqrt 3) 2))]) + (im (+ (* x x x x) (* (complex (- 2) 0) x x x) (* (complex 5 0) x x) (* (complex 4 0) x) (complex 7 0))))) diff --git a/bench/hamming/machine-decide.fpcore b/bench/hamming/machine-decide.fpcore index ef0fad635..bb84b7421 100644 --- a/bench/hamming/machine-decide.fpcore +++ b/bench/hamming/machine-decide.fpcore @@ -2,9 +2,9 @@ (FPCore (a x) :name "expax (section 3.5)" - :target + :herbie-target (if (< (fabs (* a x)) 1/10) - (* (* a x) (+ 1 (+ (/ (* a x) 2) (/ (sqr (* a x)) 6)))) + (* (* a x) (+ 1 (+ (/ (* a x) 2) (/ (pow (* a x) 2) 6)))) (- (exp (* a x)) 1)) (- (exp (* a x)) 1)) diff --git a/bench/hamming/overflow-underflow.fpcore b/bench/hamming/overflow-underflow.fpcore index 5cbf69371..4813e39cd 100644 --- a/bench/hamming/overflow-underflow.fpcore +++ b/bench/hamming/overflow-underflow.fpcore @@ -2,7 +2,7 @@ (FPCore (x) :name "expq2 (section 3.11)" - :target + :herbie-target (/ 1 (- 1 (exp (- x)))) (/ (exp x) (- (exp x) 1))) diff --git a/bench/hamming/quadratic.fpcore b/bench/hamming/quadratic.fpcore index c73404085..f080f651e 100644 --- a/bench/hamming/quadratic.fpcore +++ b/bench/hamming/quadratic.fpcore @@ -2,31 +2,31 @@ (FPCore (a b c) :name "quadp (p42, positive)" - :target - (let ([d (sqrt (- (sqr b) (* 4 (* a c))))]) + :herbie-target + (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (let ([r1 (/ (+ (- b) d) (* 2 a))] [r2 (/ (- (- b) d) (* 2 a))]) (if (< b 0) r1 (/ c (* a r2))))) - (let ([d (sqrt (- (sqr b) (* 4 (* a c))))]) + (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (+ (- b) d) (* 2 a)))) (FPCore (a b c) :name "quadm (p42, negative)" - :target - (let ((d (sqrt (- (sqr b) (* 4 (* a c)))))) + :herbie-target + (let ((d (sqrt (- (* b b) (* 4 (* a c)))))) (let ([r1 (/ (+ (- b) d) (* 2 a))] [r2 (/ (- (- b) d) (* 2 a))]) (if (< b 0) (/ c (* a r1)) r2))) - (let ([d (sqrt (- (sqr b) (* 4 (* a c))))]) + (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (- (- b) d) (* 2 a)))) -(FPCore (a b/2 c) +(FPCore (a b_2 c) :name "quad2m (problem 3.2.1, negative)" - (let ([d (sqrt (- (sqr b/2) (* a c)))]) - (/ (- (- b/2) d) a))) + (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) + (/ (- (- b_2) d) a))) -(FPCore (a b/2 c) +(FPCore (a b_2 c) :name "quad2p (problem 3.2.1, positive)" - (let ([d (sqrt (- (sqr b/2) (* a c)))]) - (/ (+ (- b/2) d) a))) + (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) + (/ (+ (- b_2) d) a))) diff --git a/bench/hamming/rearrangement.fpcore b/bench/hamming/rearrangement.fpcore index d59223a9a..885c23341 100644 --- a/bench/hamming/rearrangement.fpcore +++ b/bench/hamming/rearrangement.fpcore @@ -2,36 +2,36 @@ (FPCore (x) :name "2sqrt (example 3.1)" - :target + :herbie-target (/ 1 (+ (sqrt (+ x 1)) (sqrt x))) (- (sqrt (+ x 1)) (sqrt x))) (FPCore (x eps) :name "2sin (example 3.3)" - :target + :herbie-target (* 2 (* (cos (+ x (/ eps 2))) (sin (/ eps 2)))) (- (sin (+ x eps)) (sin x))) (FPCore (x) :name "tanhf (example 3.4)" - :herbie-expected 1 - :target + :herbie-expected 2 + :herbie-target (tan (/ x 2)) (/ (- 1 (cos x)) (sin x))) (FPCore (N) :name "2atan (example 3.5)" - :target + :herbie-target (atan (/ 1 (+ 1 (* N (+ N 1))))) (- (atan (+ N 1)) (atan N))) (FPCore (x) :name "2isqrt (example 3.6)" - :target + :herbie-target (/ 1 (+ (* (+ x 1) (sqrt x)) (* x (sqrt (+ x 1))))) (- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))) @@ -42,22 +42,21 @@ (FPCore (x eps) :name "2tan (problem 3.3.2)" - :herbie-expected 28 - :target + :herbie-target (/ (sin eps) (* (cos x) (cos (+ x eps)))) (- (tan (+ x eps)) (tan x))) (FPCore (x) :name "3frac (problem 3.3.3)" - :target - (/ 2 (* x (- (sqr x) 1))) + :herbie-target + (/ 2 (* x (- (* x x) 1))) (+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))) (FPCore (x) :name "2cbrt (problem 3.3.4)" - (- (pow (+ x 1) (/ 1 3)) (pow x (/ 1 3)))) + (- (cbrt (+ x 1)) (cbrt x))) (FPCore (x eps) :name "2cos (problem 3.3.5)" @@ -69,7 +68,7 @@ (FPCore (x) :name "exp2 (problem 3.3.7)" - :target - (* 4 (sqr (sinh (/ x 2)))) + :herbie-target + (* 4 (pow (sinh (/ x 2)) 2)) (+ (- (exp x) 2) (exp (- x)))) diff --git a/bench/hamming/series.fpcore b/bench/hamming/series.fpcore index aba21cd61..2f6ca2b18 100644 --- a/bench/hamming/series.fpcore +++ b/bench/hamming/series.fpcore @@ -3,25 +3,25 @@ (FPCore (x) :name "expm1 (example 3.7)" :pre (< -0.00017 x) - :target - (* x (+ 1 (/ x 2) (/ (sqr x) 6))) + :herbie-target + (* x (+ 1 (/ x 2) (/ (* x x) 6))) (- (exp x) 1)) (FPCore (n) :name "logs (example 3.8)" :pre (> n 6.8e+15) - :target - (- (log (+ n 1)) (- (/ 1 (* 2 n)) (- (/ 1 (* 3 (sqr n))) (/ 4 (pow n 3))))) + :herbie-target + (- (log (+ n 1)) (- (/ 1 (* 2 n)) (- (/ 1 (* 3 (* n n))) (/ 4 (pow n 3))))) (- (* (+ n 1) (log (+ n 1))) (* n (log n)) 1)) (FPCore (x) :name "invcot (example 3.9)" :pre (and (< -0.026 x) (< x 0.026)) - :target + :herbie-target (if (< (fabs x) 0.026) - (* (/ x 3) (+ 1 (/ (sqr x) 15))) + (* (/ x 3) (+ 1 (/ (* x x) 15))) (- (/ 1 x) (/ 1 (tan x)))) (- (/ 1 x) (/ 1 (tan x)))) @@ -29,19 +29,19 @@ (FPCore (x) :name "qlog (example 3.10)" :pre (and (< -1 x) (< x 1)) - :target - (- (+ 1 x (/ (sqr x) 2) (* 5/12 (pow x 3)))) + :herbie-target + (- (+ 1 x (/ (* x x) 2) (* 5/12 (pow x 3)))) (/ (log (- 1 x)) (log (+ 1 x)))) (FPCore (x) :name "cos2 (problem 3.4.1)" - (/ (- 1 (cos x)) (sqr x))) + (/ (- 1 (cos x)) (* x x))) (FPCore (a b eps) :name "expq3 (problem 3.4.2)" :pre (and (< -1 eps) (< eps 1)) - :target + :herbie-target (/ (+ a b) (* a b)) (/ @@ -50,7 +50,7 @@ (FPCore (eps) :name "logq (problem 3.4.3)" - :target + :herbie-target (* -2 (+ eps (/ (pow eps 3) 3) (/ (pow eps 5) 5))) (log (/ (- 1 eps) (+ 1 eps)))) diff --git a/bench/haskell.fpcore b/bench/haskell.fpcore index c84da1a73..7c923df13 100644 --- a/bench/haskell.fpcore +++ b/bench/haskell.fpcore @@ -2,32 +2,26 @@ (FPCore (x y) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, A" - :target - (sqrt (+ x y)) (sqrt (+ x y))) (FPCore (x y z t) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, B" - :target - (/ (- (+ x y) z) (* t 2.0)) (/ (- (+ x y) z) (* t 2.0))) (FPCore (x y) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, C" - :target - (sqrt (fabs (- x y))) (sqrt (fabs (- x y)))) (FPCore (x y z t) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, D" - :target + :herbie-target (- x (+ (* x (/ y t)) (* (- z) (/ y t)))) (+ x (/ (* y (- z x)) t))) (FPCore (x y z t a) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, E" - :target + :herbie-target (if (< y -1.0761266216389975e-10) (+ x (/ 1 (/ (/ a (- z t)) y))) (if (< y 2.894426862792089e-49) @@ -37,7 +31,7 @@ (FPCore (x y z t a) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, F" - :target + :herbie-target (if (< y -1.0761266216389975e-10) (- x (/ 1 (/ (/ a (- z t)) y))) (if (< y 2.894426862792089e-49) @@ -47,37 +41,31 @@ (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, G" - :target - (* (+ x y) (+ z 1.0)) (* (+ x y) (+ z 1.0))) (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, H" - :target - (* (+ x y) (- 1.0 z)) (* (+ x y) (- 1.0 z))) (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, I" - :target - (+ (+ x y) z) (+ (+ x y) z)) (FPCore (x y z t a b c i j) :name "Data.Colour.Matrix:determinant from colour-2.3.3, A" - :target + :herbie-target (if (< x -1.469694296777705e-64) (+ (- (* x (- (* y z) (* t a))) - (/ (* b (- (sqr (* c z)) (sqr (* t i)))) (+ (* c z) (* t i)))) + (/ (* b (- (pow (* c z) 2) (pow (* t i) 2))) (+ (* c z) (* t i)))) (* j (- (* c a) (* y i)))) (if (< x 3.2113527362226803e-147) (- (* (- (* b i) (* x a)) t) (- (* z (* c b)) (* j (- (* c a) (* y i))))) (+ (- (* x (- (* y z) (* t a))) - (/ (* b (- (sqr (* c z)) (sqr (* t i)))) (+ (* c z) (* t i)))) + (/ (* b (- (pow (* c z) 2) (pow (* t i) 2))) (+ (* c z) (* t i)))) (* j (- (* c a) (* y i)))))) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* t i)))) @@ -85,7 +73,7 @@ (FPCore (x y z t a) :name "Data.Colour.Matrix:inverse from colour-2.3.3, B" - :target + :herbie-target (if (< z -2.468684968699548e+170) (- (* (/ y a) x) (* (/ t a) z)) (if (< z 6.309831121978371e-71) @@ -95,97 +83,79 @@ (FPCore (x y) :name "Data.Colour.CIE.Chromaticity:chromaCoords from colour-2.3.3" - :target - (- (- 1.0 x) y) (- (- 1.0 x) y)) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, A" - :target - (/ (+ x y) 2.0) (/ (+ x y) 2.0)) (FPCore (x y z t a) :name "Data.Colour.RGB:hslsv from colour-2.3.3, B" - :target + :herbie-target (+ (/ 60.0 (/ (- z t) (- x y))) (* a 120.0)) (+ (/ (* 60.0 (- x y)) (- z t)) (* a 120.0))) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, C" - :target + :herbie-target (- (/ x (- 2.0 (+ x y))) (/ y (- 2.0 (+ x y)))) (/ (- x y) (- 2.0 (+ x y)))) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, D" - :target + :herbie-target (- (/ x (+ x y)) (/ y (+ x y))) (/ (- x y) (+ x y))) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, E" - :target + :herbie-target (- 1 (/ y x)) (/ (- x y) x)) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, A" - :target - (- (+ x y) (* x y)) (- (+ x y) (* x y))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, B" - :target + :herbie-target (+ x (* x y)) (* x (+ y 1.0))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, C" - :target - (- (* x 2.0) y) (- (* x 2.0) y)) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, D" - :target - (+ x (* (* (- y x) 6.0) (- (/ 2.0 3.0) z))) (+ x (* (* (- y x) 6.0) (- (/ 2.0 3.0) z)))) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, E" - :target + :herbie-target (- x (* (* 6.0 z) (- x y))) (+ x (* (* (- y x) 6.0) z))) (FPCore (x) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, F" - :target - (+ x (/ 1.0 3.0)) (+ x (/ 1.0 3.0))) (FPCore (x) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, G" - :target - (- x (/ 1.0 3.0)) (- x (/ 1.0 3.0))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, H" - :target - (* x (- 1.0 y)) (* x (- 1.0 y))) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, I" - :target - (* x (- 1.0 (* y z))) (* x (- 1.0 (* y z)))) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, J" - :target + :herbie-target (if (< (* x (- 1.0 (* (- 1.0 y) z))) -1.618195973607049e+50) (+ x (* (- 1.0 y) (* (- z) x))) (if (< (* x (- 1.0 (* (- 1.0 y) z))) 3.892237649663903e+134) @@ -195,67 +165,49 @@ (FPCore (x y) :name "Data.Colour.SRGB:invTransferFunction from colour-2.3.3" - :target - (/ (+ x y) (+ y 1.0)) (/ (+ x y) (+ y 1.0))) (FPCore (x y) :name "Data.Colour.SRGB:transferFunction from colour-2.3.3" - :target - (- (* (+ x 1.0) y) x) (- (* (+ x 1.0) y) x)) (FPCore (x) :name "Data.Colour.CIE:lightness from colour-2.3.3" - :target - (- (* x 116.0) 16.0) (- (* x 116.0) 16.0)) (FPCore (x) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, A" - :target - (+ (* (/ 841.0 108.0) x) (/ 4.0 29.0)) (+ (* (/ 841.0 108.0) x) (/ 4.0 29.0))) (FPCore (x y) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, B" - :target - (* 500.0 (- x y)) (* 500.0 (- x y))) (FPCore (x y) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, C" - :target - (* 200.0 (- x y)) (* 200.0 (- x y))) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, A" - :target + :herbie-target (* y (- (* x 3.0) 0.41379310344827586)) (* (* (- x (/ 16.0 116.0)) 3.0) y)) (FPCore (x) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, B" - :target - (/ (+ x 16.0) 116.0) (/ (+ x 16.0) 116.0)) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, C" - :target - (+ x (/ y 500.0)) (+ x (/ y 500.0))) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, D" - :target - (- x (/ y 200.0)) (- x (/ y 200.0))) (FPCore (x y z t a) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, A" - :target + :herbie-target (if (< z -32113435955957344.0) (- (/ x (- t (* a z))) (/ y (- (/ t z) a))) (if (< z 3.5139522372978296e-86) @@ -265,7 +217,7 @@ (FPCore (x y z t) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, B" - :target + :herbie-target (if (< x -1.618195973607049e+50) (/ 1 (- (/ y x) (* (/ z x) t))) (if (< x 2.1378306434876444e+131) @@ -275,13 +227,13 @@ (FPCore (x y z) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, C" - :target + :herbie-target (/ (+ x (* y z)) (/ (+ x (* y z)) (- x (* y z)))) (- x (* y z))) (FPCore (x y z) :name "Diagrams.Solve.Tridiagonal:solveCyclicTriDiagonal from diagrams-solve-0.1, A" - :target + :herbie-target (if (< z -4.262230790519429e-138) (/ (* x y) z) (if (< z 1.7042130660650472e-164) (/ x (/ z y)) (* (/ x z) y))) @@ -289,7 +241,7 @@ (FPCore (x y z t a b) :name "Diagrams.Solve.Tridiagonal:solveCyclicTriDiagonal from diagrams-solve-0.1, B" - :target + :herbie-target (if (< t -1.3659085366310088e-271) (* 1 (* (+ x (* (/ y t) z)) (/ 1 (+ (+ a 1.0) (* (/ y t) b))))) (if (< t 3.036967103737246e-130) @@ -299,25 +251,19 @@ (FPCore (x y z) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, A" - :target - (- x (* (* y 4.0) z)) (- x (* (* y 4.0) z))) (FPCore (x y z) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, B" - :target - (* (/ 1.0 2.0) (+ x (* y (sqrt z)))) (* (/ 1.0 2.0) (+ x (* y (sqrt z))))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, C" - :target - (/ x (* y 2.0)) (/ x (* y 2.0))) (FPCore (x y z t a b) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, A" - :target + :herbie-target (if (< y 7.590524218811189e-161) (+ (- (* x 2.0) (* (* (* y 9.0) z) t)) (* a (* 27.0 b))) (+ (- (* x 2.0) (* 9.0 (* y (* t z)))) (* (* a 27.0) b))) @@ -325,25 +271,25 @@ (FPCore (x y z) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, B" - :target + :herbie-target (- (* x (* 3.0 y)) z) (- (* (* x 3.0) y) z)) (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, C" - :target + :herbie-target (/ (/ x y) 3.0) (/ x (* y 3.0))) (FPCore (x y z t) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, D" - :target + :herbie-target (/ (acos (* (/ (/ x 27.0) (* y z)) (/ (sqrt t) (/ 2.0 3.0)))) 3.0) (* (/ 1.0 3.0) (acos (* (/ (* 3.0 (/ x (* y 27.0))) (* z 2.0)) (sqrt t))))) (FPCore (x y z t a b c i j k) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, E" - :target + :herbie-target (if (< t -1.6210815397541398e-69) (- (- (* (* 18.0 t) (* (* x y) z)) (* (+ (* a t) (* i x)) 4.0)) @@ -363,25 +309,23 @@ (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, F" - :target - (* (* x 27.0) y) (* (* x 27.0) y)) (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, G" - :target + :herbie-target (/ (+ x y) 2.0) (* (/ 1.0 2.0) (+ x y))) (FPCore (x y z t) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, H" - :target + :herbie-target (+ (- x (/ y (* z 3.0))) (/ (/ t (* z 3.0)) y)) (+ (- x (/ y (* z 3.0))) (/ t (* (* z 3.0) y)))) (FPCore (x y z t a) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, I" - :target + :herbie-target (if (< a -2.090464557976709e+86) (- (* 0.5 (/ (* y x) a)) (* 4.5 (/ t (/ a z)))) (if (< a 2.144030707833976e+99) @@ -391,7 +335,7 @@ (FPCore (x y z t a b c) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, J" - :target + :herbie-target (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) -1.100156740804105e-171) @@ -419,7 +363,7 @@ (FPCore (x y z t a b) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, K" - :target + :herbie-target (if (< z -1.3793337487235141e+129) (- (* (* 2.0 (sqrt x)) (cos (- (/ 1 y) (/ (/ 0.3333333333333333 z) t)))) @@ -433,135 +377,109 @@ (FPCore (x y) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, A" - :target - (- x (* (/ 3.0 8.0) y)) (- x (* (/ 3.0 8.0) y))) (FPCore (x y z t) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, B" - :target + :herbie-target (- (+ (/ x 8.0) t) (* (/ z 2.0) y)) (+ (- (* (/ 1.0 8.0) x) (/ (* y z) 2.0)) t)) (FPCore (x y z t a b c) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, C" - :target - (+ (- (+ (* x y) (/ (* z t) 16.0)) (/ (* a b) 4.0)) c) (+ (- (+ (* x y) (/ (* z t) 16.0)) (/ (* a b) 4.0)) c)) (FPCore (x y z) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, D" - :target - (- (/ (* x y) 2.0) (/ z 8.0)) (- (/ (* x y) 2.0) (/ z 8.0))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, E" - :target - (- x (/ y 4.0)) (- x (/ y 4.0))) (FPCore (x y) :name "Text.Parsec.Token:makeTokenParser from parsec-3.1.9, A" - :target - (/ (+ x y) 10.0) (/ (+ x y) 10.0)) (FPCore (x y z) :name "Text.Parsec.Token:makeTokenParser from parsec-3.1.9, B" - :target - (* (+ x y) z) (* (+ x y) z)) (FPCore (x y) :name "Numeric.Interval.Internal:bisect from intervals-0.7.1, A" - :target + :herbie-target (* 0.5 (+ x y)) (+ x (/ (- y x) 2.0))) (FPCore (x y) :name "Numeric.Interval.Internal:scale from intervals-0.7.1, B" - :target - (/ (* x y) 2.0) (/ (* x y) 2.0)) (FPCore (x y z t) :name "Linear.V2:$cdot from linear-1.19.1.3, A" - :target - (+ (* x y) (* z t)) (+ (* x y) (* z t))) (FPCore (x y z t) :name "Linear.V3:cross from linear-1.19.1.3" - :target - (- (* x y) (* z t)) (- (* x y) (* z t))) (FPCore (x y z t a b) :name "Linear.V3:$cdot from linear-1.19.1.3, B" - :target - (+ (+ (* x y) (* z t)) (* a b)) (+ (+ (* x y) (* z t)) (* a b))) (FPCore (x y z t a b c i) :name "Linear.V4:$cdot from linear-1.19.1.3, C" - :target - (+ (+ (+ (* x y) (* z t)) (* a b)) (* c i)) (+ (+ (+ (* x y) (* z t)) (* a b)) (* c i))) (FPCore (x y z) :name "Main:bigenough2 from A" - :target - (+ x (* y (+ z x))) (+ x (* y (+ z x)))) -(FPCore (x) :name "Main:bigenough1 from B" :target (+ (sqr x) x) (+ x (* x x))) +(FPCore (x) :name "Main:bigenough1 from B" (+ x (* x x))) (FPCore (x) :name "Main:bigenough3 from C" - :target + :herbie-target (/ 1.0 (+ (sqrt (+ x 1.0)) (sqrt x))) (- (sqrt (+ x 1.0)) (sqrt x))) (FPCore (x y z) :name "Linear.Quaternion:$c/ from linear-1.19.1.3, A" - :target + :herbie-target (+ (* (* 3 z) z) (* y x)) (+ (+ (+ (* x y) (* z z)) (* z z)) (* z z))) (FPCore (x y z) :name "Linear.Quaternion:$c/ from linear-1.19.1.3, B" - :target + :herbie-target (* (- x z) y) (+ (- (- (* x y) (* y z)) (* y y)) (* y y))) (FPCore (x y z) :name "Linear.Quaternion:$c/ from linear-1.19.1.3, C" - :target + :herbie-target (* (- x z) y) (- (- (+ (* x y) (* y y)) (* y z)) (* y y))) (FPCore (x y z) :name "Linear.Quaternion:$c/ from linear-1.19.1.3, D" - :target + :herbie-target (* (- x z) y) (- (+ (- (* x y) (* y y)) (* y y)) (* y z))) (FPCore (x y) :name "Linear.Quaternion:$c/ from linear-1.19.1.3, E" - :target - (+ (sqr x) (* y (+ y (+ y y)))) + :herbie-target + (+ (* x x) (* y (+ y (+ y y)))) (+ (+ (+ (* x x) (* y y)) (* y y)) (* y y))) (FPCore (x y) :name "Graphics.Rasterific.Linear:$cquadrance from Rasterific-0.6.1" - :target - (+ (sqr x) (sqr y)) (+ (* x x) (* y y))) (FPCore (x y z) :name "Linear.Quaternion:$ctanh from linear-1.19.1.3" - :target + :herbie-target (if (< z -4.2173720203427147e-29) (/ (* x (/ 1 (/ y (sin y)))) z) (if (< z 4.446702369113811e+64) @@ -571,19 +489,19 @@ (FPCore (x y) :name "Linear.Quaternion:$ccosh from linear-1.19.1.3" - :target + :herbie-target (* (sin x) (/ (sinh y) x)) (/ (* (sin x) (sinh y)) x)) (FPCore (x y) :name "Linear.Quaternion:$csinh from linear-1.19.1.3" - :target + :herbie-target (/ (* (cosh x) (sin y)) y) (* (cosh x) (/ (sin y) y))) (FPCore (x y z) :name "Linear.Quaternion:$ctan from linear-1.19.1.3" - :target + :herbie-target (if (< y -4.618902267687042e-52) (* (/ (/ y z) x) (cosh x)) (if (< y 1.038530535935153e-39) @@ -593,33 +511,27 @@ (FPCore (x y) :name "Linear.Quaternion:$ccos from linear-1.19.1.3" - :target - (* (sin x) (/ (sinh y) y)) (* (sin x) (/ (sinh y) y))) (FPCore (x y) :name "Linear.Quaternion:$csin from linear-1.19.1.3" - :target - (* (cos x) (/ (sinh y) y)) (* (cos x) (/ (sinh y) y))) (FPCore (x y) :name "Linear.Quaternion:$clog from linear-1.19.1.3" - :target + :herbie-target (if (< x -1.5097698010472593e+153) (- (+ (* 1/2 (/ y x)) x)) - (if (< x 5.582399551122541e+57) (sqrt (+ (sqr x) y)) (+ (* 1/2 (/ y x)) x))) + (if (< x 5.582399551122541e+57) (sqrt (+ (* x x) y)) (+ (* 1/2 (/ y x)) x))) (sqrt (+ (* x x) y))) (FPCore (x y) :name "Linear.Quaternion:$cexp from linear-1.19.1.3" - :target - (* x (/ (sin y) y)) (* x (/ (sin y) y))) (FPCore (x y z t a b c i j k y0 y1 y2 y3 y4 y5) :name "Linear.Matrix:det44 from linear-1.19.1.3" - :target + :herbie-target (if (< y4 -7.206256231996481e+60) (- (- @@ -725,7 +637,7 @@ (FPCore (x y z t a b c i j) :name "Linear.Matrix:det33 from linear-1.19.1.3" - :target + :herbie-target (if (< t -8.120978919195912e-33) (- (* x (- (* z y) (* a t))) @@ -733,7 +645,7 @@ (if (< t -4.712553818218485e-169) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* i a)))) - (/ (* j (- (sqr (* c t)) (sqr (* i y)))) (+ (* c t) (* i y)))) + (/ (* j (- (pow (* c t) 2) (pow (* i y) 2))) (+ (* c t) (* i y)))) (if (< t -7.633533346031584e-308) (- (* x (- (* z y) (* a t))) @@ -741,7 +653,7 @@ (if (< t 1.0535888557455487e-139) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* i a)))) - (/ (* j (- (sqr (* c t)) (sqr (* i y)))) (+ (* c t) (* i y)))) + (/ (* j (- (pow (* c t) 2) (pow (* i y) 2))) (+ (* c t) (* i y)))) (- (* x (- (* z y) (* a t))) (- (* b (- (* z c) (* a i))) (* (- (* c t) (* y i)) j))))))) @@ -751,19 +663,19 @@ (FPCore (x y) :name "Linear.Matrix:fromQuaternion from linear-1.19.1.3, A" - :target + :herbie-target (* (* x 2.0) (- x y)) (* 2.0 (- (* x x) (* x y)))) (FPCore (x y) :name "Linear.Matrix:fromQuaternion from linear-1.19.1.3, B" - :target + :herbie-target (* (* x 2.0) (+ x y)) (* 2.0 (+ (* x x) (* x y)))) (FPCore (x y z t) :name "Linear.Projection:inverseInfinitePerspective from linear-1.19.1.3" - :target + :herbie-target (if (< t -9.231879582886777e-80) (* (* y t) (- x z)) (if (< t 2.543067051564877e+83) (* y (* t (- x z))) (* (* y (- x z)) t))) @@ -771,7 +683,7 @@ (FPCore (x y z t) :name "Linear.Projection:infinitePerspective from linear-1.19.1.3, A" - :target + :herbie-target (if (< (/ (* x 2.0) (- (* y z) (* t z))) -2.559141628295061e-13) (* (/ x (* (- y t) z)) 2.0) (if (< (/ (* x 2.0) (- (* y z) (* t z))) 1.045027827330126e-269) @@ -781,25 +693,25 @@ (FPCore (x y) :name "Linear.Projection:inversePerspective from linear-1.19.1.3, B" - :target + :herbie-target (- (/ 0.5 y) (/ 0.5 x)) (/ (- x y) (* (* x 2.0) y))) (FPCore (x y) :name "Linear.Projection:inversePerspective from linear-1.19.1.3, C" - :target + :herbie-target (+ (/ 0.5 x) (/ 0.5 y)) (/ (+ x y) (* (* x 2.0) y))) (FPCore (x y) :name "Linear.Projection:perspective from linear-1.19.1.3, A" - :target + :herbie-target (/ 1 (- (/ x (+ x y)) (/ y (+ x y)))) (/ (+ x y) (- x y))) (FPCore (x y) :name "Linear.Projection:perspective from linear-1.19.1.3, B" - :target + :herbie-target (if (< x -1.7210442634149447e+81) (* (/ (* 2.0 x) (- x y)) y) (if (< x 8.364504563556443e+16) @@ -809,33 +721,31 @@ (FPCore (x y) :name "Physics.ForceLayout:coulombForce from force-layout-0.4.0.2" - :target + :herbie-target (/ (/ x y) y) (/ x (* y y))) (FPCore (x y) :name "Codec.Picture.Types:toneMapping from JuicyPixels-3.2.6.1" - :target + :herbie-target (* (/ x 1) (/ (+ (/ x y) 1.0) (+ x 1.0))) (/ (* x (+ (/ x y) 1.0)) (+ x 1.0))) (FPCore (x y z t a b) :name "Codec.Picture.Jpg.FastDct:referenceDct from JuicyPixels-3.2.6.1" - :target - (* x (cos (* (/ b 16.0) (/ t (+ (- 1.0 (* a 2.0)) (sqr (* a 2.0))))))) + :herbie-target + (* x (cos (* (/ b 16.0) (/ t (+ (- 1.0 (* a 2.0)) (pow (* a 2.0) 2)))))) (* (* x (cos (/ (* (* (+ (* y 2.0) 1.0) z) t) 16.0))) (cos (/ (* (* (+ (* a 2.0) 1.0) b) t) 16.0)))) (FPCore (x) :name "Main:i from " - :target - (+ (+ (+ (+ x x) x) x) x) (+ (+ (+ (+ x x) x) x) x)) (FPCore (x y z t) :name "Main:z from " - :target + :herbie-target (+ (+ (+ @@ -851,43 +761,35 @@ (FPCore (x y z t a b c i) :name "Diagrams.ThreeD.Shapes:frustum from diagrams-lib-1.3.0.3, A" - :target + :herbie-target (* 2.0 (- (+ (* x y) (* z t)) (* (+ a (* b c)) (* c i)))) (* 2.0 (- (+ (* x y) (* z t)) (* (* (+ a (* b c)) c) i)))) (FPCore (x y z) :name "Diagrams.ThreeD.Shapes:frustum from diagrams-lib-1.3.0.3, B" - :target - (+ x (* (- y x) z)) (+ x (* (- y x) z))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutY from diagrams-lib-1.3.0.3" - :target - (+ (* x (cos y)) (* z (sin y))) (+ (* x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutX from diagrams-lib-1.3.0.3, A" - :target - (- (* x (cos y)) (* z (sin y))) (- (* x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutX from diagrams-lib-1.3.0.3, B" - :target - (+ (* x (sin y)) (* z (cos y))) (+ (* x (sin y)) (* z (cos y)))) (FPCore (x y z) :name "Diagrams.Color.HSV:lerp from diagrams-contrib-1.3.0.5" - :target + :herbie-target (- y (* x (- y z))) (+ (* (- 1.0 x) y) (* x z))) (FPCore (x y z) :name "Diagrams.TwoD.Segment.Bernstein:evaluateBernstein from diagrams-lib-1.3.0.3" - :target + :herbie-target (if (< x -2.71483106713436e-162) (- (* (+ 1 y) (/ x z)) x) (if (< x 3.874108816439546e-197) @@ -897,65 +799,59 @@ (FPCore (x y) :name "Diagrams.Segment:$catParam from diagrams-lib-1.3.0.3, A" - :target + :herbie-target (* (* x 3.0) (* x y)) (* (* (* x 3.0) x) y)) (FPCore (x y) :name "Diagrams.Segment:$catParam from diagrams-lib-1.3.0.3, B" - :target + :herbie-target (* (* x (* 3.0 y)) y) (* (* (* x 3.0) y) y)) (FPCore (x) :name "Diagrams.Segment:$catParam from diagrams-lib-1.3.0.3, C" - :target - (* x (* x x)) (* (* x x) x)) (FPCore (x) :name "Diagrams.Tangent:$catParam from diagrams-lib-1.3.0.3, D" - :target + :herbie-target (+ 3.0 (- (* (* 9.0 x) x) (* 12.0 x))) (* 3.0 (+ (- (* (* x 3.0) x) (* x 4.0)) 1.0))) (FPCore (x) :name "Diagrams.Tangent:$catParam from diagrams-lib-1.3.0.3, E" - :target - (- (* 6.0 x) (* 9.0 (sqr x))) + :herbie-target + (- (* 6.0 x) (* 9.0 (* x x))) (* (* 3.0 (- 2.0 (* x 3.0))) x)) (FPCore (x) :name "Diagrams.Tangent:$catParam from diagrams-lib-1.3.0.3, F" - :target - (* (* 3.0 x) x) (* (* x 3.0) x)) (FPCore (x y z t) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, A" - :target + :herbie-target (/ (+ x (- (/ y (- t (/ x z))) (/ x (- (* t z) x)))) (+ x 1.0)) (/ (+ x (/ (- (* y z) x) (- (* t z) x))) (+ x 1.0))) (FPCore (x y) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, B" - :target + :herbie-target (if (< y -3693.8482788297247) - (- (/ x (sqr y)) (- (/ x y) x)) + (- (/ x (* y y)) (- (/ x y) x)) (if (< y 6799310503.41891) (/ (* x y) (+ y 1.0)) - (- (/ x (sqr y)) (- (/ x y) x)))) + (- (/ x (* y y)) (- (/ x y) x)))) (/ (* x y) (+ y 1.0))) (FPCore (x y) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, C" - :target - (/ (- x y) (- 1.0 y)) (/ (- x y) (- 1.0 y))) (FPCore (x y) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, D" - :target + :herbie-target (if (< y -3693.8482788297247) (- (/ 1.0 y) (- (/ x y) x)) (if (< y 6799310503.41891) @@ -965,33 +861,31 @@ (FPCore (x y) :name "Diagrams.TwoD.Arc:bezierFromSweepQ1 from diagrams-lib-1.3.0.3" - :target + :herbie-target (* (/ (- 1.0 x) y) (/ (- 3.0 x) 3.0)) (/ (* (- 1.0 x) (- 3.0 x)) (* y 3.0))) (FPCore (x y) :name "Diagrams.TwoD.Arc:arcBetween from diagrams-lib-1.3.0.3" - :target + :herbie-target (if (< (/ (- (* x x) (* (* y 4.0) y)) (+ (* x x) (* (* y 4.0) y))) 0.9743233849626781) (- - (/ (sqr x) (+ (sqr x) (* (sqr y) 4.0))) - (/ (* (sqr y) 4.0) (+ (sqr x) (* (sqr y) 4.0)))) + (/ (* x x) (+ (* x x) (* (* y y) 4.0))) + (/ (* (* y y) 4.0) (+ (* x x) (* (* y y) 4.0)))) (- - (sqr (/ x (sqrt (+ (sqr x) (* (sqr y) 4.0))))) - (/ (* (sqr y) 4.0) (+ (sqr x) (* (sqr y) 4.0))))) + (pow (/ x (sqrt (+ (* x x) (* (* y y) 4.0)))) 2) + (/ (* (* y y) 4.0) (+ (* x x) (* (* y y) 4.0))))) (/ (- (* x x) (* (* y 4.0) y)) (+ (* x x) (* (* y 4.0) y)))) (FPCore (x) :name "Diagrams.TwoD.Ellipse:ellipse from diagrams-lib-1.3.0.3" - :target - (sqrt (- 1.0 (sqr x))) (sqrt (- 1.0 (* x x)))) (FPCore (x y z t a) :name "Graphics.Rendering.Chart.Axis.Types:invLinMap from Chart-1.5.3" - :target + :herbie-target (if (< z -1.2536131056095036e+188) (- t (* (/ y z) (- t x))) (if (< z 4.446702369113811e+64) @@ -1001,42 +895,24 @@ (FPCore (x y z) :name "Diagrams.TwoD.Segment:bezierClip from diagrams-lib-1.3.0.3" - :target + :herbie-target (- z (* (- z x) y)) (+ (* x y) (* z (- 1.0 y)))) (FPCore (x y) :name "Data.Octree.Internal:octantDistance from Octree-0.5.4.2" - :target + :herbie-target (if (< x -1.1236950826599826e+145) (- x) - (if (< x 1.116557621183362e+93) (sqrt (+ (sqr x) (sqr y))) x)) + (if (< x 1.116557621183362e+93) (sqrt (+ (* x x) (* y y))) x)) (sqrt (+ (* x x) (* y y)))) (FPCore (x y) :name "Graphics.Rasterific.Shading:$sradialGradientWithFocusShader from Rasterific-0.6.1" - :target - (- x (sqr y)) (- x (* y y))) (FPCore (x y) :name "Diagrams.TwoD.Path.Metafont.Internal:hobbyF from diagrams-contrib-1.3.0.5" - :target - (/ - (+ - 2.0 - (* - (log - (exp - (* - (* (sqrt 2.0) (- (sin x) (/ (sin y) 16.0))) - (- (sin y) (/ (sin x) 16.0))))) - (- (cos x) (cos y)))) - (* - 3.0 - (+ - (+ 1.0 (* (/ (- (sqrt 5.0) 1.0) 2.0) (cos x))) - (* (/ (- 3.0 (sqrt 5.0)) 2.0) (cos y))))) (/ (+ 2.0 @@ -1053,7 +929,7 @@ (FPCore (x y) :name "Diagrams.TwoD.Layout.CirclePacking:approxRadius from diagrams-contrib-1.3.0.5" - :target + :herbie-target (if (< y -1.2303690911306994e+114) 1.0 (if (< y -9.102852406811914e-222) @@ -1065,7 +941,7 @@ (FPCore (x y z) :name "Diagrams.TwoD.Apollonian:descartes from diagrams-contrib-1.3.0.5" - :target + :herbie-target (if (< z 7.636950090573675e+176) (* 2.0 (sqrt (+ (* (+ x y) z) (* x y)))) (* @@ -1078,13 +954,13 @@ (FPCore (x y z) :name "Diagrams.TwoD.Apollonian:initialConfig from diagrams-contrib-1.3.0.5, A" - :target + :herbie-target (- (* y 0.5) (* (* (/ 0.5 y) (+ z x)) (- z x))) (/ (- (+ (* x x) (* y y)) (* z z)) (* y 2.0))) (FPCore (x y z) :name "Diagrams.TwoD.Apollonian:initialConfig from diagrams-contrib-1.3.0.5, B" - :target + :herbie-target (if (< y 2.5816096488251695e-278) (- (* x y)) (* x (* (sqrt (+ y z)) (sqrt (- y z))))) @@ -1092,77 +968,63 @@ (FPCore (x y z) :name "Diagrams.Backend.Rasterific:rasterificRadialGradient from diagrams-rasterific-1.3.1.3" - :target + :herbie-target (- (+ y (/ x z)) (/ y (/ z x))) (/ (+ x (* y (- z x))) z)) (FPCore (x y z t) :name "Data.HashTable.ST.Basic:computeOverhead from hashtables-1.2.0.2" - :target + :herbie-target (- (/ (+ (/ 2.0 z) 2.0) t) (- 2.0 (/ x y))) (+ (/ x y) (/ (+ 2.0 (* (* z 2.0) (- 1.0 t))) (* t z)))) (FPCore (x y z t) :name "Language.Haskell.HsColour.ColourHighlight:unbase from hscolour-1.23" - :target - (+ (* (+ (* x y) z) y) t) (+ (* (+ (* x y) z) y) t)) (FPCore (x) :name "System.Random.MWC.Distributions:blocks from mwc-random-0.13.3.2" - :target - (* (* 0.5 x) x) (* (* x 0.5) x)) (FPCore (x y) :name "System.Random.MWC.Distributions:standard from mwc-random-0.13.3.2" - :target - (* 0.5 (- (sqr x) y)) (* 0.5 (- (* x x) y))) (FPCore (x y z) :name "SynthBasics:oscSampleBasedAux from YampaSynth-0.2" - :target - (+ x (* y (- z x))) (+ x (* y (- z x)))) (FPCore (x y z t) :name "System.Random.MWC.Distributions:truncatedExp from mwc-random-0.13.3.2" - :target + :herbie-target (if (< z -2.8874623088207947e+119) (- - (- x (/ (/ (- 0.5) (* y t)) (sqr z))) - (* (/ (- 0.5) (* y t)) (/ (/ 2.0 z) (sqr z)))) + (- x (/ (/ (- 0.5) (* y t)) (* z z))) + (* (/ (- 0.5) (* y t)) (/ (/ 2.0 z) (* z z)))) (- x (/ (log (+ 1.0 (* z y))) t))) (- x (/ (log (+ (- 1.0 y) (* y (exp z)))) t))) (FPCore (x y z) :name "System.Random.MWC.Distributions:gamma from mwc-random-0.13.3.2" - :target + :herbie-target (- (+ y (* 0.5 x)) (* y (- z (log z)))) (+ (* x 0.5) (* y (+ (- 1.0 z) (log z))))) (FPCore (x y) :name "AI.Clustering.Hierarchical.Internal:average from clustering-0.2.1, A" - :target - (/ x (+ x y)) (/ x (+ x y))) (FPCore (x) :name "Numeric.Integration.TanhSinh:nonNegative from integration-0.2.1" - :target - (/ x (- 1.0 x)) (/ x (- 1.0 x))) (FPCore (x y z) :name "Graphics.Rasterific.QuadraticFormula:discriminant from Rasterific-0.6.1" - :target - (- (sqr x) (* z (* y 4.0))) (- (* x x) (* (* y 4.0) z))) (FPCore (x y z t a b) :name "Graphics.Rasterific.CubicBezier:cachedBezierAt from Rasterific-0.6.1" - :target + :herbie-target (if (< z -1.1820553527347888e+19) (+ (* z (+ (* b a) y)) (+ x (* t a))) (if (< z 4.7589743188364287e-122) @@ -1172,139 +1034,123 @@ (FPCore (x) :name "Graphics.Rasterific.CubicBezier:isSufficientlyFlat from Rasterific-0.6.1" - :target - (* (* 16.0 x) x) (* (* x 16.0) x)) (FPCore (x y z) :name "Graphics.Rasterific.Shading:$sgradientColorAt from Rasterific-0.6.1" - :target + :herbie-target (- (/ x (- z y)) (/ y (- z y))) (/ (- x y) (- z y))) (FPCore (x) :name "Graphics.Rasterific.Shading:$sradialGradientWithFocusShader from Rasterific-0.6.1, A" - :target - (+ (sqr x) 1.0) (+ (* x x) 1.0)) (FPCore (x y z t) :name "Graphics.Rasterific.Shading:$sradialGradientWithFocusShader from Rasterific-0.6.1, B" - :target - (- (sqr x) (* 4.0 (* y (- (sqr z) t)))) + :herbie-target + (- (* x x) (* 4.0 (* y (- (* z z) t)))) (- (* x x) (* (* y 4.0) (- (* z z) t)))) (FPCore (x y) :name "Data.Number.Erf:$dmerfcx from erf-2.0.0.0" - :target + :herbie-target (* x (pow (exp y) y)) (* x (exp (* y y)))) (FPCore (x y z t) :name "Data.Number.Erf:$cinvnormcdf from erf-2.0.0.0, A" - :target + :herbie-target (* (* (- (* x 0.5) y) (sqrt (* z 2.0))) (pow (exp 1) (/ (* t t) 2.0))) (* (* (- (* x 0.5) y) (sqrt (* z 2.0))) (exp (/ (* t t) 2.0)))) (FPCore (x y) :name "Data.Number.Erf:$cinvnormcdf from erf-2.0.0.0, B" - :target - (- x (/ y (+ 1.0 (/ (* x y) 2.0)))) (- x (/ y (+ 1.0 (/ (* x y) 2.0))))) (FPCore (x y z t) :name "Numeric.AD.Rank1.Halley:findZero from ad-4.2.4" - :target + :herbie-target (- x (/ 1 (- (/ z y) (/ (/ t 2.0) z)))) (- x (/ (* (* y 2.0) z) (- (* (* z 2.0) z) (* y t))))) (FPCore (x y z) :name "Crypto.Random.Test:calculate from crypto-random-0.0.9" - :target + :herbie-target (+ x (* y (/ y z))) (+ x (/ (* y y) z))) (FPCore (x) :name "Numeric.Log:$cexpm1 from log-domain-0.10.2.1, A" - :target + :herbie-target (* (* 2.0 x) x) (* (* x 2.0) x)) (FPCore (x y) :name "Numeric.Log:$cexpm1 from log-domain-0.10.2.1, B" - :target - (+ (+ (* x y) x) y) (+ (+ (* x y) x) y)) (FPCore (x y) :name "Numeric.Log:$clog1p from log-domain-0.10.2.1, A" - :target - (+ (sqr y) (+ (* 2.0 x) (sqr x))) + :herbie-target + (+ (* y y) (+ (* 2.0 x) (* x x))) (+ (+ (* x 2.0) (* x x)) (* y y))) (FPCore (x) :name "Numeric.Log:$clog1p from log-domain-0.10.2.1, B" - :target - (/ x (+ 1.0 (sqrt (+ x 1.0)))) (/ x (+ 1.0 (sqrt (+ x 1.0))))) (FPCore (x) :name "Data.Approximate.Numerics:blog from approximate-0.2.2.1" - :target + :herbie-target (/ 6.0 (/ (+ (+ x 1.0) (* 4.0 (sqrt x))) (- x 1.0))) (/ (* 6.0 (- x 1.0)) (+ (+ x 1.0) (* 4.0 (sqrt x))))) (FPCore (x) :name "Graphics.Rasterific.Svg.PathConverter:segmentToBezier from rasterific-svg-0.2.3.1, A" - :target + :herbie-target (/ (/ (* 8.0 (sin (* x 0.5))) 3.0) (/ (sin x) (sin (* x 0.5)))) (/ (* (* (/ 8.0 3.0) (sin (* x 0.5))) (sin (* x 0.5))) (sin x))) (FPCore (x y z) :name "Graphics.Rasterific.Svg.PathConverter:segmentToBezier from rasterific-svg-0.2.3.1, B" - :target - (- (+ x (cos y)) (* z (sin y))) (- (+ x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Graphics.Rasterific.Svg.PathConverter:segmentToBezier from rasterific-svg-0.2.3.1, C" - :target - (+ (+ x (sin y)) (* z (cos y))) (+ (+ x (sin y)) (* z (cos y)))) (FPCore (x y z t) :name "Graphics.Rasterific.Svg.PathConverter:arcToSegments from rasterific-svg-0.2.3.1" - :target - (+ (sqr (/ x y)) (sqr (/ z t))) + :herbie-target + (+ (pow (/ x y) 2) (pow (/ z t) 2)) (+ (/ (* x x) (* y y)) (/ (* z z) (* t t)))) (FPCore (x) :name "Development.Shake.Profile:generateTrace from shake-0.15.5" - :target + :herbie-target 0 (* 1000000.0 (- x x))) (FPCore (x y z t a b) :name "Development.Shake.Progress:decay from shake-0.15.5" - :target + :herbie-target (- (/ (+ (* z t) (* y x)) (+ y (* z (- b y)))) (/ a (+ (- b y) (/ y z)))) (/ (+ (* x y) (* z (- t a))) (+ y (* z (- b y))))) (FPCore (x y) :name "Development.Shake.Progress:message from shake-0.15.5" - :target + :herbie-target (* (/ x 1) (/ 100.0 (+ x y))) (/ (* x 100.0) (+ x y))) (FPCore (x y z) :name "Diagrams.Backend.Rasterific:$crender from diagrams-rasterific-1.3.1.3" - :target - (+ (* x y) (* (- 1.0 x) z)) (+ (* x y) (* (- 1.0 x) z))) (FPCore (x y z t) :name "Numeric.Histogram:binBounds from Chart-1.5.3" - :target + :herbie-target (if (< x -9.025511195533005e-135) (- x (* (/ z t) (- x y))) (if (< x 4.275032163700715e-250) @@ -1314,19 +1160,17 @@ (FPCore (x y z) :name "Graphics.Rendering.Chart.Drawing:drawTextsR from Chart-1.5.3" - :target - (+ (* x y) (* (- x 1.0) z)) (+ (* x y) (* (- x 1.0) z))) (FPCore (x y) :name "Graphics.Rendering.Chart.Axis.Types:hBufferRect from Chart-1.5.3" - :target + :herbie-target (- (* 1.5 x) (* 0.5 y)) (+ x (/ (- x y) 2.0))) (FPCore (x y z t a) :name "Graphics.Rendering.Chart.Axis.Types:linMap from Chart-1.5.3" - :target + :herbie-target (if (< a -1.6153062845442575e-142) (+ x (* (/ (- y x) 1) (/ (- z t) (- a t)))) (if (< a 3.774403170083174e-182) @@ -1336,37 +1180,35 @@ (FPCore (x y) :name "Graphics.Rendering.Chart.Plot.Vectors:renderPlotVectors from Chart-1.5.3" - :target + :herbie-target (- (* y x) (- y 1.0)) (+ x (* (- 1.0 x) (- 1.0 y)))) (FPCore (x y) :name "Graphics.Rendering.Chart.Plot.AreaSpots:renderSpotLegend from Chart-1.5.3" - :target - (+ x (/ (fabs (- y x)) 2.0)) (+ x (/ (fabs (- y x)) 2.0))) (FPCore (x y z t) :name "Graphics.Rendering.Chart.Plot.AreaSpots:renderAreaSpots4D from Chart-1.5.3" - :target + :herbie-target (/ x (/ (- t z) (- y z))) (/ (* x (- y z)) (- t z))) (FPCore (x y) :name "Graphics.Rendering.Chart.Plot.Pie:renderPie from Chart-1.5.3" - :target + :herbie-target (- y 0) (- (+ x y) x)) (FPCore (x y z t a) :name "Graphics.Rendering.Chart.SparkLine:renderSparkLine from Chart-1.5.3" - :target + :herbie-target (- x (* (/ (- y z) (+ (- t z) 1.0)) a)) (- x (/ (- y z) (/ (+ (- t z) 1.0) a)))) (FPCore (x y z) :name "Graphics.Rendering.Chart.Backend.Diagrams:calcFontMetrics from Chart-diagrams-1.5.1, A" - :target + :herbie-target (if (< y -3.7429310762689856e+171) (* (/ (+ y x) (- y)) z) (if (< y 3.5534662456086734e+168) @@ -1376,7 +1218,7 @@ (FPCore (x y z t) :name "Graphics.Rendering.Chart.Backend.Diagrams:calcFontMetrics from Chart-diagrams-1.5.1, B" - :target + :herbie-target (if (< (/ (* (/ y z) t) t) -1.20672205123045e+245) (/ y (/ z x)) (if (< (/ (* (/ y z) t) t) -5.907522236933906e-275) @@ -1390,13 +1232,11 @@ (FPCore (x y) :name "AI.Clustering.Hierarchical.Internal:average from clustering-0.2.1, B" - :target - (/ x (+ y x)) (/ x (+ y x))) (FPCore (x y z t a b) :name "AI.Clustering.Hierarchical.Internal:ward from clustering-0.2.1" - :target + :herbie-target (if (< (/ (- (+ (* (+ x y) z) (* (+ t y) a)) (* y b)) (+ (+ x t) y)) -3.5813117084150564e+153) @@ -1410,33 +1250,27 @@ (FPCore (x y z) :name "Numeric.SpecFunctions:invErfc from math-functions-0.1.5.2, A" - :target + :herbie-target (+ x (/ 1 (- (* (/ 1.1283791670955126 y) (exp z)) x))) (+ x (/ y (- (* 1.1283791670955126 (exp z)) (* x y))))) (FPCore (x) :name "Numeric.SpecFunctions:invErfc from math-functions-0.1.5.2, B" - :target - (* - 0.70711 - (- (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* x (+ 0.99229 (* x 0.04481))))) x)) (* 0.70711 (- (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* x (+ 0.99229 (* x 0.04481))))) x))) (FPCore (x y) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, A" - :target - (+ (- (* x (- y 1.0)) (* y 0.5)) 0.918938533204673) (+ (- (* x (- y 1.0)) (* y 0.5)) 0.918938533204673)) (FPCore (x y z) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, B" - :target + :herbie-target (if (< z -8120153.652456675) (- (* (+ (/ 0.07512208616047561 z) 0.0692910599291889) y) - (- (/ (* 0.40462203869992125 y) (sqr z)) x)) + (- (/ (* 0.40462203869992125 y) (* z z)) x)) (if (< z 6.576118972787377e+20) (+ x @@ -1449,7 +1283,7 @@ (/ 1 (+ (* (+ z 6.012459259764103) z) 3.350343815022304)))) (- (* (+ (/ 0.07512208616047561 z) 0.0692910599291889) y) - (- (/ (* 0.40462203869992125 y) (sqr z)) x)))) + (- (/ (* 0.40462203869992125 y) (* z z)) x)))) (+ x (/ @@ -1462,9 +1296,9 @@ (FPCore (x y z) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, C" - :target + :herbie-target (if (< x -3.326128725870005e+62) - (- (+ (/ y (sqr x)) (* 4.16438922228 x)) 110.1139242984811) + (- (+ (/ y (* x x)) (* 4.16438922228 x)) 110.1139242984811) (if (< x 9.429991714554673e+55) (* (/ (- x 2.0) 1) @@ -1479,11 +1313,11 @@ (+ (* (+ - (+ (* 263.505074721 x) (+ (* 43.3400022514 (sqr x)) (* x (* x x)))) + (+ (* 263.505074721 x) (+ (* 43.3400022514 (* x x)) (* x (* x x)))) 313.399215894) x) 47.066876606))) - (- (+ (/ y (sqr x)) (* 4.16438922228 x)) 110.1139242984811))) + (- (+ (/ y (* x x)) (* 4.16438922228 x)) 110.1139242984811))) (/ (* (- x 2.0) @@ -1498,11 +1332,11 @@ (FPCore (x y z t a b) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, D" - :target + :herbie-target (if (< z -6.499344996252632e+53) (+ x - (* (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (sqr z))) (/ y 1))) + (* (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (* z z))) (/ y 1))) (if (< z 7.066965436914287e+59) (+ x @@ -1520,7 +1354,7 @@ (+ x (* - (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (sqr z))) + (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (* z z))) (/ y 1))))) (+ x @@ -1534,13 +1368,13 @@ (FPCore (x) :name "Numeric.SpecFunctions:$slogFactorial from math-functions-0.1.5.2, A" - :target + :herbie-target (/ (/ 1.0 x) x) (/ 1.0 (* x x))) (FPCore (x y z) :name "Numeric.SpecFunctions:$slogFactorial from math-functions-0.1.5.2, B" - :target + :herbie-target (+ (+ (+ (* (- x 0.5) (log x)) (- 0.91893853320467 x)) (/ 0.083333333333333 x)) (* (/ z x) (- (* z (+ y 0.0007936500793651)) 0.0027777777777778))) @@ -1554,107 +1388,91 @@ (FPCore (x y z t a) :name "Numeric.SpecFunctions:logGammaL from math-functions-0.1.5.2" - :target + :herbie-target (+ (log (+ x y)) (+ (- (log z) t) (* (- a 0.5) (log t)))) (+ (- (+ (log (+ x y)) (log z)) t) (* (- a 0.5) (log t)))) (FPCore (x) :name "Numeric.SpecFunctions:logGammaCorrection from math-functions-0.1.5.2" - :target - (- (* (sqr x) 2.0) 1.0) (- (* (* x x) 2.0) 1.0)) (FPCore (x y) :name "Numeric.SpecFunctions:log1p from math-functions-0.1.5.2, A" - :target - (* x (- 1.0 (* x y))) (* x (- 1.0 (* x y)))) (FPCore (x) :name "Numeric.SpecFunctions:log1p from math-functions-0.1.5.2, B" - :target - (* x (- 1.0 (* x 0.5))) (* x (- 1.0 (* x 0.5)))) (FPCore (x y z t a b) :name "Numeric.SpecFunctions:logBeta from math-functions-0.1.5.2, A" - :target - (+ (+ (+ x y) (/ (* (- 1 (sqr (log t))) z) (+ 1 (log t)))) (* (- a 0.5) b)) + :herbie-target + (+ (+ (+ x y) (/ (* (- 1 (pow (log t) 2)) z) (+ 1 (log t)))) (* (- a 0.5) b)) (+ (- (+ (+ x y) z) (* z (log t))) (* (- a 0.5) b))) (FPCore (x y z t a b c i) :name "Numeric.SpecFunctions:logBeta from math-functions-0.1.5.2, B" - :target - (+ (+ (+ (+ (+ (* x (log y)) z) t) a) (* (- b 0.5) (log c))) (* y i)) (+ (+ (+ (+ (+ (* x (log y)) z) t) a) (* (- b 0.5) (log c))) (* y i))) (FPCore (x y z) :name "Numeric.SpecFunctions:choose from math-functions-0.1.5.2" - :target + :herbie-target (/ x (/ z (+ y z))) (/ (* x (+ y z)) z)) (FPCore (x y z) :name "Numeric.SpecFunctions:stirlingError from math-functions-0.1.5.2" - :target + :herbie-target (- (- (+ y x) z) (* (+ y 0.5) (log y))) (- (+ (- x (* (+ y 0.5) (log y))) y) z)) (FPCore (x y z t) :name "Numeric.SpecFunctions:incompleteGamma from math-functions-0.1.5.2, A" - :target - (+ (- (- (* x (log y)) y) z) (log t)) (+ (- (- (* x (log y)) y) z) (log t))) (FPCore (x y) :name "Numeric.SpecFunctions:incompleteGamma from math-functions-0.1.5.2, B" - :target + :herbie-target (* 3.0 (+ (* y (sqrt x)) (* (- (/ 1.0 (* x 9.0)) 1.0) (sqrt x)))) (* (* 3.0 (sqrt x)) (- (+ y (/ 1.0 (* x 9.0))) 1.0))) (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteGamma from math-functions-0.1.5.2, A" - :target - (- 1.0 (* x (+ 0.253 (* x 0.12)))) (- 1.0 (* x (+ 0.253 (* x 0.12))))) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteGamma from math-functions-0.1.5.2, B" - :target + :herbie-target (if (< y -81284752.61947241) - (- 1.0 (log (- (/ x (sqr y)) (- (/ 1.0 y) (/ x y))))) + (- 1.0 (log (- (/ x (* y y)) (- (/ 1.0 y) (/ x y))))) (if (< y 3.0094271212461764e+25) (log (/ (exp 1.0) (- 1.0 (/ (- x y) (- 1.0 y))))) - (- 1.0 (log (- (/ x (sqr y)) (- (/ 1.0 y) (/ x y))))))) + (- 1.0 (log (- (/ x (* y y)) (- (/ 1.0 y) (/ x y))))))) (- 1.0 (log (- 1.0 (/ (- x y) (- 1.0 y)))))) (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteGamma from math-functions-0.1.5.2, C" - :target - (- (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* x (+ 0.99229 (* x 0.04481))))) x) (- (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* x (+ 0.99229 (* x 0.04481))))) x)) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteGamma from math-functions-0.1.5.2, D" - :target + :herbie-target (- (- 1.0 (/ (/ 1.0 x) 9.0)) (/ y (* 3.0 (sqrt x)))) (- (- 1.0 (/ 1.0 (* x 9.0))) (/ y (* 3.0 (sqrt x))))) (FPCore (x y) :name "Numeric.SpecFunctions:incompleteBetaApprox from math-functions-0.1.5.2, A" - :target + :herbie-target (/ (/ (/ x (+ (+ y 1) x)) (+ y x)) (/ 1 (/ y (+ y x)))) (/ (* x y) (* (* (+ x y) (+ x y)) (+ (+ x y) 1.0)))) (FPCore (x y z t a b) :name "Numeric.SpecFunctions:incompleteBetaApprox from math-functions-0.1.5.2, B" - :target - (* x (exp (+ (* y (- (log z) t)) (* a (- (log (- 1.0 z)) b))))) (* x (exp (+ (* y (- (log z) t)) (* a (- (log (- 1.0 z)) b)))))) (FPCore (x y z t a b) :name "Numeric.SpecFunctions:incompleteBetaWorker from math-functions-0.1.5.2, A" - :target + :herbie-target (if (< t -0.8845848504127471) (/ (* x (/ (pow a (- t 1.0)) y)) (- (+ b 1) (* y (log z)))) (if (< t 852031.2288374073) @@ -1664,17 +1482,17 @@ (FPCore (x y z t) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, B" - :target + :herbie-target (- (* (- z) - (+ (+ (* 0.5 (sqr y)) y) (* (/ 1/3 (* 1.0 (* 1.0 1.0))) (* y (* y y))))) + (+ (+ (* 0.5 (* y y)) y) (* (/ 1/3 (* 1.0 (* 1.0 1.0))) (* y (* y y))))) (- t (* x (log y)))) (- (+ (* x (log y)) (* z (log (- 1.0 y)))) t)) (FPCore (x y z t) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, C" - :target + :herbie-target (if (< (* x (- (/ y z) (/ t (- 1.0 z)))) -7.623226303312042e-196) (* x (- (/ y z) (* t (/ 1 (- 1.0 z))))) (if (< (* x (- (/ y z) (/ t (- 1.0 z)))) 1.4133944927702302e-211) @@ -1684,19 +1502,15 @@ (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, D" - :target - (- x (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* (+ 0.99229 (* x 0.04481)) x)))) (- x (/ (+ 2.30753 (* x 0.27061)) (+ 1.0 (* (+ 0.99229 (* x 0.04481)) x))))) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, E" - :target - (+ (- 1.0 x) (* y (sqrt x))) (+ (- 1.0 x) (* y (sqrt x)))) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, F" - :target + :herbie-target (if (< y -3.7311844206647956e+94) (/ (exp (/ -1 y)) x) (if (< y 2.817959242728288e+37) @@ -1708,7 +1522,7 @@ (FPCore (x y z) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, G" - :target + :herbie-target (if (< (/ y (+ z y)) 7.1154157597908e-315) (+ x (/ (exp (/ -1 z)) y)) (+ x (/ (exp (log (pow (/ y (+ y z)) y))) y))) @@ -1716,13 +1530,11 @@ (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, H" - :target - (/ (- (sqr x) 3.0) 6.0) (/ (- (* x x) 3.0) 6.0)) (FPCore (x y z t a b c) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, I" - :target + :herbie-target (if (< t -2.118326644891581e-50) (/ x @@ -1743,7 +1555,7 @@ (* (- (* (+ (/ 5.0 6.0) a) (* 3.0 t)) 2.0) (* (- a (/ 5.0 6.0)) (* (- b c) t)))) - (* (* (sqr t) 3.0) (- a (/ 5.0 6.0))))))))) + (* (* (* t t) 3.0) (- a (/ 5.0 6.0))))))))) (/ x (+ @@ -1771,7 +1583,7 @@ (FPCore (x y z) :name "Numeric.SpecFunctions.Extra:bd0 from math-functions-0.1.5.2" - :target + :herbie-target (if (< y 7.595077799083773e-308) (- (* x (log (/ x y))) z) (- (* x (- (log x) (log y))) z)) @@ -1779,165 +1591,135 @@ (FPCore (x y z t a b c i) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2" - :target - (/ - (+ (* (+ (* (+ (* (+ (* x y) z) y) 27464.7644705) y) 230661.510616) y) t) - (+ (* (+ (* (+ (* (+ y a) y) b) y) c) y) i)) (/ (+ (* (+ (* (+ (* (+ (* x y) z) y) 27464.7644705) y) 230661.510616) y) t) (+ (* (+ (* (+ (* (+ y a) y) b) y) c) y) i))) (FPCore (x y z t a) :name "Statistics.Math.RootFinding:ridders from math-functions-0.1.5.2" - :target + :herbie-target (if (< z -3.1921305903852764e+46) (- (* y x)) (if (< z 5.976268120920894e+90) - (/ (* x z) (/ (sqrt (- (sqr z) (* a t))) y)) + (/ (* x z) (/ (sqrt (- (* z z) (* a t))) y)) (* y x))) (/ (* (* x y) z) (sqrt (- (* z z) (* t a))))) (FPCore (x y z) :name "Statistics.Distribution.Poisson.Internal:probability from math-functions-0.1.5.2" - :target + :herbie-target (exp (+ (- x z) (* (log y) y))) (exp (- (+ x (* y (log y))) z))) (FPCore (x) :name "Statistics.Distribution.Binomial:directEntropy from math-functions-0.1.5.2" - :target - (* x (log x)) (* x (log x))) (FPCore (x) :name "Statistics.Correlation.Kendall:numOfTiesBy from math-functions-0.1.5.2" - :target - (- (sqr x) x) + :herbie-target + (- (* x x) x) (* x (- x 1.0))) (FPCore (x y z) :name "Statistics.Sample:robustSumVarWeighted from math-functions-0.1.5.2" - :target - (+ x (* (* y z) z)) (+ x (* (* y z) z))) (FPCore (x y z) :name "Statistics.Sample:$swelfordMean from math-functions-0.1.5.2" - :target - (+ x (/ (- y x) z)) (+ x (/ (- y x) z))) (FPCore (x y) :name "Statistics.Sample:$skurtosis from math-functions-0.1.5.2" - :target + :herbie-target (- (/ (/ x y) y) 3.0) (- (/ x (* y y)) 3.0)) (FPCore (x y z t a b) :name "Statistics.Distribution.Beta:$centropy from math-functions-0.1.5.2" - :target - (+ (- (- x (* (- y 1.0) z)) (* (- t 1.0) a)) (* (- (+ y t) 2.0) b)) (+ (- (- x (* (- y 1.0) z)) (* (- t 1.0) a)) (* (- (+ y t) 2.0) b))) (FPCore (x y z) :name "Statistics.Distribution.Beta:$cvariance from math-functions-0.1.5.2" - :target + :herbie-target (if (< z 249.6182814532307) - (/ (* y (/ x z)) (+ z (sqr z))) + (/ (* y (/ x z)) (+ z (* z z))) (/ (* (/ (/ y z) (+ 1 z)) x) z)) (/ (* x y) (* (* z z) (+ z 1.0)))) (FPCore (x y z t) :name "Statistics.Distribution.Beta:$cdensity from math-functions-0.1.5.2" - :target - (- (+ (* (- x 1.0) (log y)) (* (- z 1.0) (log (- 1.0 y)))) t) (- (+ (* (- x 1.0) (log y)) (* (- z 1.0) (log (- 1.0 y)))) t)) (FPCore (x y) :name "Statistics.Distribution.Binomial:$cvariance from math-functions-0.1.5.2" - :target - (* (* x y) (- 1.0 y)) (* (* x y) (- 1.0 y))) (FPCore (x y z) :name "Statistics.Distribution.Poisson:$clogProbability from math-functions-0.1.5.2" - :target - (- (- (* x (log y)) z) y) (- (- (* x (log y)) z) y)) (FPCore (x y z) :name "Statistics.Distribution.CauchyLorentz:$cdensity from math-functions-0.1.5.2" - :target + :herbie-target (if (< (* y (+ 1.0 (* z z))) -inf.0) - (/ (/ 1.0 y) (* (+ 1.0 (sqr z)) x)) + (/ (/ 1.0 y) (* (+ 1.0 (* z z)) x)) (if (< (* y (+ 1.0 (* z z))) 8.680743250567252e+305) - (/ (/ 1.0 x) (* (+ 1.0 (sqr z)) y)) - (/ (/ 1.0 y) (* (+ 1.0 (sqr z)) x)))) + (/ (/ 1.0 x) (* (+ 1.0 (* z z)) y)) + (/ (/ 1.0 y) (* (+ 1.0 (* z z)) x)))) (/ (/ 1.0 x) (* y (+ 1.0 (* z z))))) (FPCore (x y) :name "Examples.Basics.BasicTests:f3 from sbv-4.4" - :target - (+ (sqr x) (+ (sqr y) (* 2 (* y x)))) + :herbie-target + (+ (* x x) (+ (* y y) (* 2 (* y x)))) (* (+ x y) (+ x y))) (FPCore (x y) :name "Examples.Basics.BasicTests:f2 from sbv-4.4" - :target - (- (sqr x) (sqr y)) (- (* x x) (* y y))) (FPCore (x y) :name "Examples.Basics.BasicTests:f1 from sbv-4.4" - :target - (* (+ x y) (- x y)) (* (+ x y) (- x y))) (FPCore (x y) :name "Examples.Basics.ProofTests:f4 from sbv-4.4" - :target - (+ (sqr x) (+ (sqr y) (* (* x y) 2.0))) + :herbie-target + (+ (* x x) (+ (* y y) (* (* x y) 2.0))) (+ (+ (* x x) (* (* x 2.0) y)) (* y y))) (FPCore (x y) :name "Numeric.LinearAlgebra.Util:formatSparse from hmatrix-0.16.1.5" - :target - (/ (fabs (- x y)) (fabs y)) (/ (fabs (- x y)) (fabs y))) (FPCore (x y) :name "Data.Random.Distribution.Normal:normalF from random-fu-0.2.6.2" - :target - (exp (* (* x y) y)) (exp (* (* x y) y))) (FPCore (x y) :name "Data.Random.Distribution.Normal:normalTail from random-fu-0.2.6.2" - :target - (+ (+ y y) (sqr x)) + :herbie-target + (+ (+ y y) (* x x)) (+ (+ (* x x) y) y)) (FPCore (x) :name "Data.Random.Distribution.Normal:doubleStdNormalZ from random-fu-0.2.6.2" - :target - (- (+ x x) 1.0) (- (+ x x) 1.0)) (FPCore (x y) :name "Data.Random.Distribution.T:$ccdf from random-fu-0.2.6.2" - :target + :herbie-target (+ (* 1/2 (/ x y)) 1/2) (/ (+ x y) (+ y y))) (FPCore (x y z t) :name "Data.Random.Distribution.Triangular:triangularCDF from random-fu-0.2.6.2, A" - :target - (- 1.0 (/ x (* (- y z) (- y t)))) (- 1.0 (/ x (* (- y z) (- y t))))) (FPCore (x y z t) :name "Data.Random.Distribution.Triangular:triangularCDF from random-fu-0.2.6.2, B" - :target + :herbie-target (if (< (/ x (* (- y z) (- t z))) 0.0) (/ (/ x (- y z)) (- t z)) (* x (/ 1 (* (- y z) (- t z))))) @@ -1945,49 +1727,37 @@ (FPCore (x) :name "Data.Random.Dice:roll from dice-0.1" - :target - (- (sqr x) 1.0) (- (* x x) 1.0)) (FPCore (x) :name "Prelude:atanh from fay-base-0.20.0.1" - :target - (/ (+ x 1.0) (- 1.0 x)) (/ (+ x 1.0) (- 1.0 x))) (FPCore (x) :name "ReportTypes:explainFloat from gipeda-0.1.2.1" - :target + :herbie-target 0 (* 100.0 (/ (- x x) x))) (FPCore (x y z t a) :name "Hakyll.Web.Tags:renderTagCloud from hakyll-4.7.2.3" - :target - (+ x (* (/ (- y z) (- (+ t 1.0) z)) (- a x))) (+ x (* (/ (- y z) (- (+ t 1.0) z)) (- a x)))) (FPCore (x y z) :name "Data.Histogram.Bin.BinF:$cfromIndex from histogram-fill-0.8.4.1" - :target - (+ (+ (/ x 2.0) (* y x)) z) (+ (+ (/ x 2.0) (* y x)) z)) (FPCore (x y) :name "Data.Histogram.Bin.LogBinD:$cbinSizeN from histogram-fill-0.8.4.1" - :target - (- (* x y) x) (- (* x y) x)) (FPCore (x y z t a) :name "Numeric.Signal:interpolate from hsignal-0.2.7.1" - :target - (+ x (* (- y z) (/ (- t x) (- a z)))) (+ x (* (- y z) (/ (- t x) (- a z))))) (FPCore (x y z t) :name "Numeric.Signal.Multichannel:$cget from hsignal-0.2.7.1" - :target + :herbie-target (if (< z 2.759456554562692e-282) (+ (* (/ x y) (- z t)) t) (if (< z 2.326994450874436e-110) @@ -1997,25 +1767,23 @@ (FPCore (x y z t) :name "Numeric.Signal.Multichannel:$cput from hsignal-0.2.7.1" - :target + :herbie-target (/ t (/ (- z y) (- x y))) (* (/ (- x y) (- z y)) t)) (FPCore (x y) :name "Data.HyperLogLog.Config:hll from hyperloglog-0.3.4" - :target - (* (* x y) y) (* (* x y) y)) (FPCore (x y) :name "Data.HyperLogLog.Type:size from hyperloglog-0.3.4, A" - :target + :herbie-target (* x (log (- 1.0 (/ y x)))) (* (* x 1.0) (log (- 1.0 (/ y x))))) (FPCore (x y) :name "Data.HyperLogLog.Type:size from hyperloglog-0.3.4, B" - :target + :herbie-target (if (< y 1.2973149052617803e-303) (* x (log (/ x y))) (/ x (/ 1 (- (log x) (log y))))) @@ -2023,7 +1791,7 @@ (FPCore (x y z) :name "Diagrams.Backend.Cairo.Internal:setTexture from diagrams-cairo-1.3.0.3" - :target + :herbie-target (if (< z -2.060202331921739e+104) (- x (/ (* z x) y)) (if (< z 1.6939766013828526e+213) (/ x (/ y (- y z))) (* (- y z) (/ x y)))) @@ -2031,49 +1799,43 @@ (FPCore (x y) :name "Numeric.Integration.TanhSinh:simpson from integration-0.2.1" - :target - (* x (+ y y)) (* x (+ y y))) (FPCore (x y) :name "Numeric.Integration.TanhSinh:everywhere from integration-0.2.1" - :target + :herbie-target (+ x (* (* x y) y)) (* x (+ 1.0 (* y y)))) (FPCore (x y z t) :name "Data.Metrics.Snapshot:quantile from metrics-0.3.0.2" - :target + :herbie-target (+ x (+ (* t (- y z)) (* (- x) (- y z)))) (+ x (* (- y z) (- t x)))) (FPCore (x y) :name "Graphics.Rendering.Plot.Render.Plot.Legend:renderLegendOutside from plot-0.2.3.4, A" - :target + :herbie-target (+ y (* 2 x)) (+ (+ x y) x)) (FPCore (x y z t) :name "Graphics.Rendering.Plot.Render.Plot.Legend:renderLegendOutside from plot-0.2.3.4, B" - :target - (+ (* x (+ (+ (+ (+ y z) z) y) t)) (* y 5.0)) (+ (* x (+ (+ (+ (+ y z) z) y) t)) (* y 5.0))) (FPCore (x y z) :name "Graphics.Rendering.Plot.Render.Plot.Legend:renderLegendInside from plot-0.2.3.4" - :target - (+ (+ (+ (+ (+ x y) y) x) z) x) (+ (+ (+ (+ (+ x y) y) x) z) x)) (FPCore (x y z) :name "Graphics.Rendering.Plot.Render.Plot.Legend:renderLegendOutside from plot-0.2.3.4, C" - :target + :herbie-target (+ (* (+ x 5.0) z) (* x y)) (+ (* x (+ y z)) (* z 5.0))) (FPCore (x y z t) :name "Graphics.Rendering.Plot.Render.Plot.Axis:tickPosition from plot-0.2.3.4" - :target + :herbie-target (if (< (* (- y x) (/ z t)) -1013646692435.8867) (+ x (/ (- y x) (/ t z))) (if (< (* (- y x) (/ z t)) -0.0) @@ -2083,13 +1845,13 @@ (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisLine from plot-0.2.3.4, A" - :target + :herbie-target (+ x (/ y (/ (- z a) (- z t)))) (+ x (* y (/ (- z t) (- z a))))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisLine from plot-0.2.3.4, B" - :target + :herbie-target (if (< y -8.508084860551241e-17) (+ x (* y (/ (- z t) (- a t)))) (if (< y 2.894426862792089e-49) @@ -2099,7 +1861,7 @@ (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTick from plot-0.2.3.4, A" - :target + :herbie-target (if (< t -1.0682974490174067e-39) (+ x (* (/ (- y z) (- a z)) t)) (if (< t 3.9110949887586375e-141) @@ -2109,7 +1871,7 @@ (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTick from plot-0.2.3.4, B" - :target + :herbie-target (if (< (- (+ x y) (/ (* (- z t) y) (- a t))) -1.3664970889390727e-07) (- (+ y x) (* (* (- z t) (/ 1 (- a t))) y)) (if (< (- (+ x y) (/ (* (- z t) y) (- a t))) 1.4754293444577233e-239) @@ -2119,60 +1881,56 @@ (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTicks from plot-0.2.3.4, A" - :target + :herbie-target (+ x (/ y (/ (- z a) (- z t)))) (+ x (/ (* y (- z t)) (- z a)))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTicks from plot-0.2.3.4, B" - :target + :herbie-target (+ x (/ y (/ (- a t) (- z t)))) (+ x (/ (* y (- z t)) (- a t)))) (FPCore (x y z) :name "Data.Array.Repa.Algorithms.Pixel:doubleRmsOfRGB8 from repa-algorithms-3.4.0.1" - :target + :herbie-target (if (< z -6.396479394109776e+136) (/ (- z) (sqrt 3.0)) (if (< z 7.320293694404182e+117) - (/ (sqrt (+ (+ (sqr z) (sqr x)) (sqr y))) (sqrt 3.0)) + (/ (sqrt (+ (+ (* z z) (* x x)) (* y y))) (sqrt 3.0)) (* (sqrt 0.3333333333333333) z))) (sqrt (/ (+ (+ (* x x) (* y y)) (* z z)) 3.0))) (FPCore (x y z) :name "Data.Array.Repa.Algorithms.ColorRamp:rampColorHotToCold from repa-algorithms-3.4.0.1, A" - :target - (+ 1.0 (/ (* 4.0 (- (+ x (* y 0.75)) z)) y)) (+ 1.0 (/ (* 4.0 (- (+ x (* y 0.75)) z)) y))) (FPCore (x y z) :name "Data.Array.Repa.Algorithms.ColorRamp:rampColorHotToCold from repa-algorithms-3.4.0.1, B" - :target + :herbie-target (- (* 4.0 (/ x z)) (+ 2.0 (* 4.0 (/ y z)))) (/ (* 4.0 (- (- x y) (* z 0.5))) z)) (FPCore (x y z) :name "Data.Array.Repa.Algorithms.ColorRamp:rampColorHotToCold from repa-algorithms-3.4.0.1, C" - :target - (+ 1.0 (/ (* 4.0 (- (+ x (* y 0.25)) z)) y)) (+ 1.0 (/ (* 4.0 (- (+ x (* y 0.25)) z)) y))) (FPCore (x) :name "Data.Spline.Key:interpolateKeys from smoothie-0.4.0.2" - :target + :herbie-target (* x (* x (- 3.0 (* x 2.0)))) (* (* x x) (- 3.0 (* x 2.0)))) (FPCore (x y z) :name "FRP.Yampa.Vector3:vector3Rho from Yampa-0.10.2" - :target + :herbie-target (if (< z -6.396479394109776e+136) (- z) - (if (< z 7.320293694404182e+117) (sqrt (+ (+ (sqr z) (sqr x)) (sqr y))) z)) + (if (< z 7.320293694404182e+117) (sqrt (+ (+ (* z z) (* x x)) (* y y))) z)) (sqrt (+ (+ (* x x) (* y y)) (* z z)))) (FPCore (x y z t) :name "SynthBasics:moogVCF from YampaSynth-0.2" - :target + :herbie-target (+ x (* y (* z (- (tanh (/ t y)) (tanh (/ x y)))))) (+ x (* (* y z) (- (tanh (/ t y)) (tanh (/ x y)))))) diff --git a/bench/libraries/fast-math.fpcore b/bench/libraries/fast-math.fpcore index 517034f2c..9b48c6cf5 100644 --- a/bench/libraries/fast-math.fpcore +++ b/bench/libraries/fast-math.fpcore @@ -2,47 +2,47 @@ (FPCore (d1 d2 d3) :name "FastMath dist" - :target + :herbie-target (* d1 (+ d2 d3)) (+ (* d1 d2) (* d1 d3))) (FPCore (d) :name "FastMath test1" - :target + :herbie-target (* d 30) (+ (* d 10) (* d 20))) (FPCore (d1 d2) :name "FastMath test2" - :target + :herbie-target (* d1 (+ 30 d2)) (+ (* d1 10) (* d1 d2) (* d1 20))) (FPCore (d1 d2 d3) :name "FastMath dist3" - :target + :herbie-target (* d1 (+ 37 d3 d2)) (+ (* d1 d2) (* (+ d3 5) d1) (* d1 32))) (FPCore (d1 d2 d3 d4) :name "FastMath dist4" - :target + :herbie-target (* d1 (- (+ (- d2 d3) d4) d1)) (- (+ (- (* d1 d2) (* d1 d3)) (* d4 d1)) (* d1 d1))) (FPCore (d1 d2 d3) :name "FastMath test3" - :target + :herbie-target (* d1 (+ 3 d2 d3)) (+ (* d1 3) (* d1 d2) (* d1 d3))) (FPCore (d1) :name "FastMath repmul" - :target (pow d1 4) + :herbie-target (pow d1 4) (* d1 d1 d1 d1)) (FPCore (d1) :name "FastMath test5" - :target + :herbie-target (pow d1 10) (* d1 (* d1 (* d1 d1) d1 d1 (* d1 d1) d1) d1)) diff --git a/bench/libraries/jmatjs.fpcore b/bench/libraries/jmatjs.fpcore index bc0ca9f1f..172d3b74d 100644 --- a/bench/libraries/jmatjs.fpcore +++ b/bench/libraries/jmatjs.fpcore @@ -42,7 +42,7 @@ (FPCore (wj x) :name "Jmat.Real.lambertw, newton loop step" - :target + :herbie-target (let ((ew (exp wj))) (- wj (- (/ wj (+ wj 1)) (/ x (+ ew (* wj ew)))))) (let ((ew (exp wj))) diff --git a/bench/libraries/mathjs/arithmetic.fpcore b/bench/libraries/mathjs/arithmetic.fpcore index a23cf0c41..c0be790ea 100644 --- a/bench/libraries/mathjs/arithmetic.fpcore +++ b/bench/libraries/mathjs/arithmetic.fpcore @@ -6,7 +6,7 @@ (FPCore (x) :name "math.cube on real" - :target (pow x 3) + :herbie-target (pow x 3) (* (* x x) x)) (FPCore (x.re x.im) @@ -99,9 +99,9 @@ (FPCore (re im) :name "math.sqrt on complex, real part" - :target + :herbie-target (if (< re 0) - (* 0.5 (* (sqrt 2) (sqrt (/ (sqr im) (- (sqrt (+ (sqr re) (sqr im))) re))))) + (* 0.5 (* (sqrt 2) (sqrt (/ (* im im) (- (sqrt (+ (* re re) (* im im))) re))))) (* 0.5 (sqrt (* 2.0 (+ (sqrt (+ (* re re) (* im im))) re))))) (* 0.5 (sqrt (* 2.0 (+ (sqrt (+ (* re re) (* im im))) re))))) diff --git a/bench/libraries/mathjs/trigonometry.fpcore b/bench/libraries/mathjs/trigonometry.fpcore index c2e6d2659..7bdea8ea1 100644 --- a/bench/libraries/mathjs/trigonometry.fpcore +++ b/bench/libraries/mathjs/trigonometry.fpcore @@ -6,7 +6,7 @@ (FPCore (re im) :name "math.cos on complex, imaginary part" - :target + :herbie-target (if (< (fabs im) 1) (- (* (sin re) (+ im (* 1/6 im im im) (* 1/120 im im im im im)))) (* (* 0.5 (sin re)) (- (exp (- im)) (exp im)))) @@ -18,7 +18,7 @@ (FPCore (re im) :name "math.sin on complex, imaginary part" - :target + :herbie-target (if (< (fabs im) 1) (- (* (cos re) (+ im (* 1/6 im im im) (* 1/120 im im im im im)))) (* (* 0.5 (cos re)) (- (exp (- 0 im)) (exp im)))) diff --git a/bench/mathematics/arvind.fpcore b/bench/mathematics/arvind.fpcore index aa8472c29..9fd389bc4 100644 --- a/bench/mathematics/arvind.fpcore +++ b/bench/mathematics/arvind.fpcore @@ -2,18 +2,18 @@ (FPCore (a b) :name "Exp of sum of logs" - :target + :herbie-target (* a b) (exp (+ (log a) (log b)))) (FPCore (a b) :name "Quotient of sum of exps" - :target + :herbie-target (/ 1 (+ 1 (exp (- b a)))) (/ (exp a) (+ (exp a) (exp b)))) (FPCore (a1 a2 b1 b2) :name "Quotient of products" - :target + :herbie-target (* (/ a1 b1) (/ a2 b2)) (/ (* a1 a2) (* b1 b2))) diff --git a/bench/mathematics/dirichlet-mixture-model.fpcore b/bench/mathematics/dirichlet-mixture-model.fpcore index f9cd15d41..415e64402 100644 --- a/bench/mathematics/dirichlet-mixture-model.fpcore +++ b/bench/mathematics/dirichlet-mixture-model.fpcore @@ -3,7 +3,7 @@ (FPCore (c_p c_n t s) :pre (and (< 0 c_p) (< 0 c_n)) :name "Harley's example" - :target + :herbie-target (* (pow (/ (+ 1 (exp (- t))) (+ 1 (exp (- s)))) c_p) (pow (/ (+ 1 (exp t)) (+ 1 (exp s))) c_n)) diff --git a/bench/mathematics/hyperbolic-functions.fpcore b/bench/mathematics/hyperbolic-functions.fpcore index e0112dc64..7c3fcbd7e 100644 --- a/bench/mathematics/hyperbolic-functions.fpcore +++ b/bench/mathematics/hyperbolic-functions.fpcore @@ -14,15 +14,15 @@ (FPCore (x) :name "Hyperbolic arcsine" - :target + :herbie-target (if (< x 0) - (log (/ -1 (- x (sqrt (+ (sqr x) 1))))) - (log (+ x (sqrt (+ (sqr x) 1))))) - (log (+ x (sqrt (+ (sqr x) 1))))) + (log (/ -1 (- x (sqrt (+ (* x x) 1))))) + (log (+ x (sqrt (+ (* x x) 1))))) + (log (+ x (sqrt (+ (* x x) 1))))) (FPCore (x) :name "Hyperbolic arc-cosine" - (log (+ x (sqrt (- (sqr x) 1))))) + (log (+ x (sqrt (- (* x x) 1))))) (FPCore (x) :name "Hyperbolic arc-(co)tangent" @@ -30,4 +30,4 @@ (FPCore (x) :name "Hyperbolic arc-(co)secant" - (log (+ (/ 1 x) (/ (sqrt (- 1 (sqr x))) x)))) + (log (+ (/ 1 x) (/ (sqrt (- 1 (* x x))) x)))) diff --git a/bench/mathematics/latlong.fpcore b/bench/mathematics/latlong.fpcore index 149a00ecd..265af14b0 100644 --- a/bench/mathematics/latlong.fpcore +++ b/bench/mathematics/latlong.fpcore @@ -6,7 +6,7 @@ (let ((dphi (- phi1 phi2))) (let ((a (+ - (sqr (sin (/ dphi 2))) + (pow (sin (/ dphi 2)) 2) (* (cos phi1) (cos phi2) @@ -25,7 +25,7 @@ :name "Equirectangular approximation to distance on a great circle" (let ((x (* (- lambda1 lambda2) (cos (/ (+ phi1 phi2) 2))))) (let ((y (- phi1 phi2))) - (let ((d (* R (sqrt (+ (sqr x) (sqr y)))))) + (let ((d (* R (sqrt (+ (* x x) (* y y)))))) d)))) (FPCore (lambda1 lambda2 phi1 phi2) @@ -43,7 +43,7 @@ (let ((phim (atan2 (+ (sin phi1) (sin phi2)) - (sqrt (+ (sqr (+ (cos phi1) Bx)) (sqr By))))) + (sqrt (+ (pow (+ (cos phi1) Bx) 2) (* By By))))) (lambdam (+ lambda1 (atan2 By (+ (cos phi1) Bx))))) lambdam)))) diff --git a/bench/mathematics/logistic-regression.fpcore b/bench/mathematics/logistic-regression.fpcore index 65742b51c..9f4600a0c 100644 --- a/bench/mathematics/logistic-regression.fpcore +++ b/bench/mathematics/logistic-regression.fpcore @@ -2,8 +2,12 @@ (FPCore (x y) :name "Logistic regression 2" - :target + :herbie-target (if (<= x 0) (- (log (+ 1 (exp x))) (* x y)) (- (log (+ 1 (exp (- x)))) (* (- x) (- 1 y)))) (- (log (+ 1 (exp x))) (* x y))) + +(FPCore (x y) + :name "Logistic function from Lakshay Garg" + (- (/ 2 (+ 1 (exp (* -2 x)))) 1)) diff --git a/bench/mathematics/sarnoff.fpcore b/bench/mathematics/sarnoff.fpcore new file mode 100644 index 000000000..bd6129821 --- /dev/null +++ b/bench/mathematics/sarnoff.fpcore @@ -0,0 +1,72 @@ +;; From Jeffrey Sarnoff + +(FPCore (a b c) + :name "Quadratic roots, full range" + (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a))) + +(FPCore (a b c) + :name "Quadratic roots, narrow range" + :pre (and (< 1.0536712127723509e-8 a 9.490626562425156e7) + (< 1.0536712127723509e-8 b 9.490626562425156e7) + (< 1.0536712127723509e-8 c 9.490626562425156e7)) + (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a))) + +(FPCore (a b c) + :name "Quadratic roots, medium range" + :pre (and (< 1.1102230246251565e-16 a 9.007199254740992e15) + (< 1.1102230246251565e-16 b 9.007199254740992e15) + (< 1.1102230246251565e-16 c 9.007199254740992e15)) + (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a))) + +(FPCore (a b c) + :name "Quadratic roots, wide range" + :pre (and (< 4.930380657631324e-32 a 2.028240960365167e31) + (< 4.930380657631324e-32 b 2.028240960365167e31) + (< 4.930380657631324e-32 c 2.028240960365167e31)) + (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a))) + +(FPCore (a b c d) + :name "Cubic critical" + (/ (+ (- b) (sqrt (- (* b b) (* 3 a c)))) (* 3 a))) + +(FPCore (a b c d) + :name "Cubic critical, narrow range" + :pre (and (< 1.0536712127723509e-8 a 9.490626562425156e7) + (< 1.0536712127723509e-8 b 9.490626562425156e7) + (< 1.0536712127723509e-8 c 9.490626562425156e7)) + (/ (+ (- b) (sqrt (- (* b b) (* 3 a c)))) (* 3 a))) + +(FPCore (a b c d) + :name "Cubic critical, medium range" + :pre (and (< 1.1102230246251565e-16 a 9.007199254740992e15) + (< 1.1102230246251565e-16 b 9.007199254740992e15) + (< 1.1102230246251565e-16 c 9.007199254740992e15)) + (/ (+ (- b) (sqrt (- (* b b) (* 3 a c)))) (* 3 a))) + +(FPCore (a b c d) + :name "Cubic critical, wide range" + :pre (and (< 4.930380657631324e-32 a 2.028240960365167e31) + (< 4.930380657631324e-32 b 2.028240960365167e31) + (< 4.930380657631324e-32 c 2.028240960365167e31)) + (/ (+ (- b) (sqrt (- (* b b) (* 3 a c)))) (* 3 a))) + +(FPCore (x) + :name "Asymptote A" (- (/ 1 (+ x 1)) (/ 1 (- x 1)))) +(FPCore (x) + :name "Asymptote B" (+ (/ 1 (- x 1)) (/ x (+ x 1)))) +(FPCore (x) + :name "Asymptote C" (- (/ x (+ x 1)) (/ (+ x 1) (- x 1)))) + +(FPCore (a b) + :name "Eccentricity of an ellipse" + :pre (<= 0 b a 1) + (sqrt (fabs (/ (- (* a a) (* b b)) (* a a))))) + +(FPCore (e v) + :name "Trigonometry A" + :pre (<= 0 e 1) + (/ (* e (sin v)) (+ 1 (* e (cos v))))) + +(FPCore (x) + :name "Trigonometry B" + (/ (- 1 (* (tan x) (tan x))) (+ 1 (* (tan x) (tan x))))) diff --git a/bench/mathematics/statistics.fpcore b/bench/mathematics/statistics.fpcore new file mode 100644 index 000000000..de5f85fc1 --- /dev/null +++ b/bench/mathematics/statistics.fpcore @@ -0,0 +1,13 @@ + +(FPCore (g h a) + :name "2-ancestry mixing, positive discriminant" + (+ (cbrt (* (/ 1 (* 2 a)) (+ (- g) (sqrt (- (* g g) (* h h)))))) + (cbrt (* (/ 1 (* 2 a)) (- (- g) (sqrt (- (* g g) (* h h)))))))) + +(FPCore (g a) + :name "2-ancestry mixing, zero discriminant" + (cbrt (/ g (* 2 a)))) + +(FPCore (g h) + :name "2-ancestry mixing, negative discriminant" + (* 2 (cos (+ (/ (* 2 PI) 3) (/ (acos (/ (- g) h)) 3))))) diff --git a/bench/numerics/every-cs.fpcore b/bench/numerics/every-cs.fpcore index 898336629..592e08ca6 100644 --- a/bench/numerics/every-cs.fpcore +++ b/bench/numerics/every-cs.fpcore @@ -2,46 +2,46 @@ (FPCore (a b c) :name "The quadratic formula (r1)" - :target - (let ((d (- (sqr b) (* 4 a c)))) + :herbie-target + (let ((d (- (* b b) (* 4 a c)))) (let ((r1 (/ (+ (- b) (sqrt d)) (* 2 a)))) (let ((r2 (/ (- (- b) (sqrt d)) (* 2 a)))) (if (< b 0) r1 (/ c (* a r2)))))) - (let ((d (- (sqr b) (* 4 a c)))) + (let ((d (- (* b b) (* 4 a c)))) (/ (+ (- b) (sqrt d)) (* 2 a)))) (FPCore (a b c) :name "The quadratic formula (r2)" - :target - (let ((d (sqrt (- (sqr b) (* 4 (* a c)))))) + :herbie-target + (let ((d (sqrt (- (* b b) (* 4 (* a c)))))) (let ((r1 (/ (+ (- b) d) (* 2 a)))) (let ((r2 (/ (- (- b) d) (* 2 a)))) (if (< b 0) (/ c (* a r1)) r2)))) - (let ((d (sqrt (- (sqr b) (* 4 (* a c)))))) + (let ((d (sqrt (- (* b b) (* 4 (* a c)))))) (/ (- (- b) d) (* 2 a)))) (FPCore (a b) :name "Difference of squares" - :target + :herbie-target (* (+ a b) (- a b)) - (- (sqr a) (sqr b))) + (- (* a a) (* b b))) (FPCore (a b c) - :pre (and (< 0 a) (< 0 b) (< 0 c)) :name "Area of a triangle" - :target + :pre (and (< 0 a (+ b c)) (< 0 b (+ a c)) (< 0 c (+ a b))) + :herbie-target (/ (sqrt (* (+ a (+ b c)) (- c (- a b)) (+ c (- a b)) (+ a (- b c)))) 4) (let ((s (/ (+ a b c) 2))) (sqrt (* s (- s a) (- s b) (- s c))))) (FPCore (x) :name "ln(1 + x)" - :target + :herbie-target (if (== (+ 1 x) 1) x (/ (* x (log (+ 1 x))) (- (+ 1 x) 1))) (log (+ 1 x))) (FPCore (i n) :name "Compound Interest" - :target + :herbie-target (let ((lnbase (if (== (+ 1 (/ i n)) 1) (/ i n) @@ -51,25 +51,25 @@ (FPCore (x) :name "x / (x^2 + 1)" - :target + :herbie-target (/ 1 (+ x (/ 1 x))) - (/ x (+ (sqr x) 1))) + (/ x (+ (* x x) 1))) (FPCore (a b c d) :name "Complex division, real part" - :target + :herbie-target (if (< (fabs d) (fabs c)) (/ (+ a (* b (/ d c))) (+ c (* d (/ d c)))) (/ (+ b (* a (/ c d))) (+ d (* c (/ c d))))) - (/ (+ (* a c) (* b d)) (+ (sqr c) (sqr d)))) + (/ (+ (* a c) (* b d)) (+ (* c c) (* d d)))) (FPCore (a b c d) :name "Complex division, imag part" - :target + :herbie-target (if (< (fabs d) (fabs c)) (/ (- b (* a (/ d c))) (+ c (* d (/ d c)))) (/ (+ (- a) (* b (/ c d))) (+ d (* c (/ c d))))) - (/ (- (* b c) (* a d)) (+ (sqr c) (sqr d)))) + (/ (- (* b c) (* a d)) (+ (* c c) (* d d)))) (FPCore (x) :name "arccos" diff --git a/bench/numerics/fma.fpcore b/bench/numerics/fma.fpcore index 6f5a21dfd..58b4a0949 100644 --- a/bench/numerics/fma.fpcore +++ b/bench/numerics/fma.fpcore @@ -3,7 +3,7 @@ (FPCore (t) :name "fma_test1" :pre (<= 0.9 t 1.1) - :target + :herbie-target (let ([x (+ 1 (* t 2e-16))] [z (- -1 (* 2 (* t 2e-16)))]) (fma x x z)) @@ -14,7 +14,7 @@ (FPCore (t) :name "fma_test2" :pre (<= 1.9 t 2.1) - :target + :herbie-target (let ([x 1.7e308]) (fma x t (- x))) (let ([x 1.7e308]) diff --git a/bench/numerics/hamming-misc.fpcore b/bench/numerics/hamming-misc.fpcore index 2a1625f25..8bad6f39c 100644 --- a/bench/numerics/hamming-misc.fpcore +++ b/bench/numerics/hamming-misc.fpcore @@ -10,7 +10,7 @@ (FPCore (a b) :name "NMSE Section 6.1 mentioned, B" - (* (/ PI 2) (/ 1 (- (sqr b) (sqr a))) (- (/ 1 a) (/ 1 b)))) + (* (/ PI 2) (/ 1 (- (* b b) (* a a))) (- (/ 1 a) (/ 1 b)))) (FPCore (x y) :name "Radioactive exchange between two surfaces" diff --git a/bench/numerics/kahan.fpcore b/bench/numerics/kahan.fpcore index 26b89a6f4..1bba5dff9 100644 --- a/bench/numerics/kahan.fpcore +++ b/bench/numerics/kahan.fpcore @@ -2,7 +2,7 @@ (FPCore (x) :name "Kahan's exp quotient" - :target + :herbie-target (if (and (< x 1) (> x -1)) (/ (- (exp x) 1) (log (exp x))) (/ (- (exp x) 1) x)) diff --git a/bench/numerics/libm.fpcore b/bench/numerics/libm.fpcore index 06f5f80ba..9eaae0d05 100644 --- a/bench/numerics/libm.fpcore +++ b/bench/numerics/libm.fpcore @@ -2,6 +2,6 @@ (FPCore (x y z) :name "simple fma test" - :target + :herbie-target -1 (- (fma x y z) (+ 1 (+ (* x y) z)))) diff --git a/bench/numerics/martel.fpcore b/bench/numerics/martel.fpcore index c3439e9fe..1572dab6d 100644 --- a/bench/numerics/martel.fpcore +++ b/bench/numerics/martel.fpcore @@ -2,7 +2,7 @@ (FPCore () :name "Rectangular parallelepiped of dimension a×b×c" - :target + :herbie-target (let ([d 2] [a 1] [b (/ 1 9)] [c (/ 1 9)]) (+ (+ (* (* c a) d) (* d (* b c))) (* d (* a b)))) (let ([d 2] [a 1] [b (/ 1 9)] [c (/ 1 9)]) @@ -11,41 +11,41 @@ (FPCore (a b c d) :pre (and (<= 56789 a 98765) (<= 0 b 1) (<= 0 c 0.0016773) (<= 0 d 0.0016773)) :name "Expression, p14" - :target + :herbie-target (+ (* a b) (* a (+ c d))) (* a (+ (+ b c) d))) (FPCore (a b c d e) :pre (<= 1 a 2 b 4 c 8 d 16 e 32) :name "Expression 1, p15" - :target + :herbie-target (+ (+ d (+ c (+ a b))) e) (+ (+ (+ (+ e d) c) b) a)) (FPCore (x) :pre (<= 0 x 2) :name "Expression 2, p15" - :target + :herbie-target (* (+ 1.0 x) x) (+ x (* x x))) (FPCore (x) :pre (<= 0 x 2) :name "Expression 3, p15" - :target + :herbie-target (* (* (+ 1.0 x) x) x) (+ (* x (* x x)) (* x x))) (FPCore (a b) :pre (and (<= 5 a 10) (<= 0 b 0.001)) :name "Expression 4, p15" - :target + :herbie-target (+ (+ (+ (* b a) (* b b)) (* b a)) (* a a)) (* (+ a b) (+ a b))) (FPCore (a b c d) :pre (and (<= -14 a -13) (<= -3 b -2) (<= 3 c 3.5) (<= 12.5 d 13.5)) :name "Expression, p6" - :target + :herbie-target (let ((e 2)) (+ (* (+ a b) e) (* (+ c d) e))) (let ((e 2)) (* (+ a (+ b (+ c d))) e))) diff --git a/bench/numerics/polynomial-cancellation.fpcore b/bench/numerics/polynomial-cancellation.fpcore index 5d99e42c7..3aac8c2bc 100644 --- a/bench/numerics/polynomial-cancellation.fpcore +++ b/bench/numerics/polynomial-cancellation.fpcore @@ -5,6 +5,6 @@ (let ([x 77617] [y 33096]) (+ (* 333.75 (pow y 6)) - (* (sqr x) (+ (* 11 (sqr x) (sqr y)) (- (pow y 6)) (* -121 (pow y 4)) -2)) + (* (* x x) (+ (* 11 (* x x) (* y y)) (- (pow y 6)) (* -121 (pow y 4)) -2)) (* 5.5 (pow y 8)) (/ x (* 2 y))))) diff --git a/bench/physics/dimer-escape.fpcore b/bench/physics/dimer-escape.fpcore index 517d736ca..2758ebfcf 100644 --- a/bench/physics/dimer-escape.fpcore +++ b/bench/physics/dimer-escape.fpcore @@ -2,7 +2,7 @@ (FPCore (J K U) :name "Maksimov and Kolovsky, Equation (3)" - (* -2 J (cos (/ K 2)) (sqrt (+ 1 (sqr (/ U (* 2 J (cos (/ K 2))))))))) + (* -2 J (cos (/ K 2)) (sqrt (+ 1 (pow (/ U (* 2 J (cos (/ K 2)))) 2))))) (FPCore (J l K U) :name "Maksimov and Kolovsky, Equation (4)" @@ -12,4 +12,4 @@ :name "Maksimov and Kolovsky, Equation (32)" (* (cos (- (/ (* K (+ m n)) 2) M)) - (exp (- (- (sqr (- (/ (+ m n) 2) M))) (- l (fabs (- m n))))))) + (exp (- (- (pow (- (/ (+ m n) 2) M) 2)) (- l (fabs (- m n))))))) diff --git a/bench/physics/multiphoton-states.fpcore b/bench/physics/multiphoton-states.fpcore index 6dff0af77..179b8b5cb 100644 --- a/bench/physics/multiphoton-states.fpcore +++ b/bench/physics/multiphoton-states.fpcore @@ -6,4 +6,4 @@ (FPCore (a1 a2 th) :name "Migdal et al, Equation (64)" - (+ (* (/ (cos th) (sqrt 2)) (sqr a1)) (* (/ (cos th) (sqrt 2)) (sqr a2)))) + (+ (* (/ (cos th) (sqrt 2)) (* a1 a1)) (* (/ (cos th) (sqrt 2)) (* a2 a2)))) diff --git a/bench/physics/quantum-walk.fpcore b/bench/physics/quantum-walk.fpcore index 5905dfa7b..6afb769d0 100644 --- a/bench/physics/quantum-walk.fpcore +++ b/bench/physics/quantum-walk.fpcore @@ -3,21 +3,21 @@ (FPCore (v t) :name "Falkner and Boettcher, Equation (20:1,3)" (/ - (- 1 (* 5 (sqr v))) - (* PI t (sqrt (* 2 (- 1 (* 3 (sqr v))))) (- 1 (sqr v))))) + (- 1 (* 5 (* v v))) + (* PI t (sqrt (* 2 (- 1 (* 3 (* v v))))) (- 1 (* v v))))) (FPCore (v) :name "Falkner and Boettcher, Equation (22+)" - (/ 4 (* 3 PI (- 1 (sqr v)) (sqrt (- 2 (* 6 (sqr v))))))) + (/ 4 (* 3 PI (- 1 (* v v)) (sqrt (- 2 (* 6 (* v v))))))) (FPCore (a k m) :name "Falkner and Boettcher, Appendix A" - (/ (* a (pow k m)) (+ 1 (* 10 k) (sqr k)))) + (/ (* a (pow k m)) (+ 1 (* 10 k) (* k k)))) (FPCore (v) :name "Falkner and Boettcher, Appendix B, 1" - (acos (/ (- 1 (* 5 (sqr v))) (- (sqr v) 1)))) + (acos (/ (- 1 (* 5 (* v v))) (- (* v v) 1)))) (FPCore (v) :name "Falkner and Boettcher, Appendix B, 2" - (* (/ (sqrt 2) 4) (sqrt (- 1 (* 3 (sqr v)))) (- 1 (sqr v)))) + (* (/ (sqrt 2) 4) (sqrt (- 1 (* 3 (* v v)))) (- 1 (* v v)))) diff --git a/bench/physics/superfluidity-breakdown.fpcore b/bench/physics/superfluidity-breakdown.fpcore index 9e42ab605..c0fc6e03b 100644 --- a/bench/physics/superfluidity-breakdown.fpcore +++ b/bench/physics/superfluidity-breakdown.fpcore @@ -2,7 +2,7 @@ (FPCore (t l Om Omc) :name "Toniolo and Linder, Equation (2)" - (asin (sqrt (/ (- 1 (sqr (/ Om Omc))) (+ 1 (* 2 (sqr (/ t l)))))))) + (asin (sqrt (/ (- 1 (pow (/ Om Omc) 2)) (+ 1 (* 2 (pow (/ t l) 2))))))) (FPCore (l Om kx ky) :name "Toniolo and Linder, Equation (3a)" @@ -13,26 +13,26 @@ 1 (/ (sqrt - (+ 1 (* (sqr (/ (* 2 l) Om)) (+ (sqr (sin kx)) (sqr (sin ky))))))))))) + (+ 1 (* (pow (/ (* 2 l) Om) 2) (+ (pow (sin kx) 2) (pow (sin ky) 2)))))))))) (FPCore (kx ky th) :name "Toniolo and Linder, Equation (3b), real" - (* (/ (sin ky) (sqrt (+ (sqr (sin kx)) (sqr (sin ky))))) (sin th))) + (* (/ (sin ky) (sqrt (+ (pow (sin kx) 2) (pow (sin ky) 2)))) (sin th))) (FPCore (x l t) :name "Toniolo and Linder, Equation (7)" (/ (* (sqrt 2) t) - (sqrt (- (* (/ (+ x 1) (- x 1)) (+ (sqr l) (* 2 (sqr t)))) (sqr l))))) + (sqrt (- (* (/ (+ x 1) (- x 1)) (+ (* l l) (* 2 (* t t)))) (* l l))))) (FPCore (t l k) :name "Toniolo and Linder, Equation (10+)" - (/ 2 (* (/ (pow t 3) (sqr l)) (sin k) (tan k) (+ (+ 1 (sqr (/ k t))) 1)))) + (/ 2 (* (/ (pow t 3) (* l l)) (sin k) (tan k) (+ (+ 1 (pow (/ k t) 2)) 1)))) (FPCore (t l k) :name "Toniolo and Linder, Equation (10-)" - (/ 2 (* (/ (pow t 3) (sqr l)) (sin k) (tan k) (- (+ 1 (sqr (/ k t))) 1)))) + (/ 2 (* (/ (pow t 3) (* l l)) (sin k) (tan k) (- (+ 1 (pow (/ k t) 2)) 1)))) (FPCore (n U t l Om U*) :name "Toniolo and Linder, Equation (13)" - (sqrt (* 2 n U (- t (* 2 (/ (sqr l) Om)) (* n (sqr (/ l Om)) (- U U*)))))) + (sqrt (* 2 n U (- t (* 2 (/ (* l l) Om)) (* n (pow (/ l Om) 2) (- U U*)))))) diff --git a/bench/physics/tea-flows.fpcore b/bench/physics/tea-flows.fpcore index cd384cbe0..6491e9f0d 100644 --- a/bench/physics/tea-flows.fpcore +++ b/bench/physics/tea-flows.fpcore @@ -2,7 +2,7 @@ (FPCore (F l) :name "VandenBroeck and Keller, Equation (6)" - (- (* PI l) (* (/ (sqr F)) (tan (* PI l))))) + (- (* PI l) (* (/ (* F F)) (tan (* PI l))))) (FPCore (f) :name "VandenBroeck and Keller, Equation (20)" @@ -15,7 +15,7 @@ :name "VandenBroeck and Keller, Equation (23)" (+ (- (* x (/ 1 (tan B)))) - (* (/ F (sin B)) (pow (+ (sqr F) 2 (* 2 x)) (- (/ 1 2)))))) + (* (/ F (sin B)) (pow (+ (* F F) 2 (* 2 x)) (- (/ 1 2)))))) (FPCore (B x) :name "VandenBroeck and Keller, Equation (24)" diff --git a/bench/physics/tea-whistle.fpcore b/bench/physics/tea-whistle.fpcore index 2aff778c8..422cc570d 100644 --- a/bench/physics/tea-whistle.fpcore +++ b/bench/physics/tea-whistle.fpcore @@ -6,16 +6,16 @@ (FPCore (w0 M D h l d) :name "Henrywood and Agarwal, Equation (9a)" - (* w0 (sqrt (- 1 (* (sqr (/ (* M D) (* 2 d))) (/ h l)))))) + (* w0 (sqrt (- 1 (* (pow (/ (* M D) (* 2 d)) 2) (/ h l)))))) (FPCore (d h l M D) :name "Henrywood and Agarwal, Equation (12)" (* (pow (/ d h) (/ 1 2)) (pow (/ d l) (/ 1 2)) - (- 1 (* (/ 1 2) (sqr (/ (* M D) (* 2 d))) (/ h l))))) + (- 1 (* (/ 1 2) (pow (/ (* M D) (* 2 d)) 2) (/ h l))))) (FPCore (c0 w h D d M) :name "Henrywood and Agarwal, Equation (13)" - (let ((x (/ (* c0 (sqr d)) (* w h (sqr D))))) - (* (/ c0 (* 2 w)) (+ x (sqrt (- (sqr x) (sqr M))))))) + (let ((x (/ (* c0 (* d d)) (* w h (* D D))))) + (* (/ c0 (* 2 w)) (+ x (sqrt (- (* x x) (* M M))))))) diff --git a/bench/physics/universal-linear-optics.fpcore b/bench/physics/universal-linear-optics.fpcore index 702ec4d2d..c255c41cc 100644 --- a/bench/physics/universal-linear-optics.fpcore +++ b/bench/physics/universal-linear-optics.fpcore @@ -4,18 +4,18 @@ :name "Bouland and Aaronson, Equation (24)" (- (+ - (sqr (+ (sqr a) (sqr b))) - (* 4 (+ (* (sqr a) (- 1 a)) (* (sqr b) (+ 3 a))))) + (pow (+ (* a a) (* b b)) 2) + (* 4 (+ (* (* a a) (- 1 a)) (* (* b b) (+ 3 a))))) 1)) (FPCore (a b) :name "Bouland and Aaronson, Equation (25)" (- (+ - (sqr (+ (sqr a) (sqr b))) - (* 4 (+ (* (sqr a) (+ 1 a)) (* (sqr b) (- 1 (* 3 a)))))) + (pow (+ (* a a) (* b b)) 2) + (* 4 (+ (* (* a a) (+ 1 a)) (* (* b b) (- 1 (* 3 a)))))) 1)) (FPCore (a b) :name "Bouland and Aaronson, Equation (26)" - (- (+ (sqr (+ (sqr a) (sqr b))) (* 4 (sqr b))) 1)) + (- (+ (pow (+ (* a a) (* b b)) 2) (* 4 (* b b))) 1)) diff --git a/bench/regression/crash.fpcore b/bench/regression/crash.fpcore index 80c770252..afaf19c7a 100644 --- a/bench/regression/crash.fpcore +++ b/bench/regression/crash.fpcore @@ -3,3 +3,16 @@ (FPCore (x y z t) :name "Graphics.Rendering.Chart.Backend.Diagrams:calcFontMetrics from Chart-diagrams-1.5.1" (* x (/ (* (/ y z) t) t))) + +(FPCore (x y z a) + :pre (and (or (== x 0) (<= 0.5884142 x 505.5909)) + (or (<= -1.796658e+308 y -9.425585e-310) (<= 1.284938e-309 y 1.751224e+308)) + (or (<= -1.776707e+308 z -8.599796e-310) (<= 3.293145e-311 z 1.725154e+308)) + (or (<= -1.796658e+308 a -9.425585e-310) (<= 1.284938e-309 a 1.751224e+308))) + (+ x (- (tan (+ y z)) (tan a)))) + +(FPCore (x cos sin) + :name "cos(2*x)/(cos^2(x)*sin^2(x))" + (/ (cos (* 2 x)) (* (pow cos 2) (* (* x (pow sin 2)) x)))) + +(FPCore (x) :pre (or (== x 0) (== x 10)) x) diff --git a/bench/regression/timeout.fpcore b/bench/regression/timeout.fpcore index 7cea45133..bf510ff5c 100644 --- a/bench/regression/timeout.fpcore +++ b/bench/regression/timeout.fpcore @@ -1,66 +1,13 @@ ; -*- mode: scheme -*- -(FPCore (a b c) - :name "Random Jason Timeout Test 001" - (+ c (asin (cosh c)))) - -(FPCore (a b c d) - :name "Random Jason Timeout Test 002" - (fmod (sinh c) (- c (sqr -2.9807307601812193e+165)))) - -(FPCore (a b c) - :name "Random Jason Timeout Test 003" - (sin (pow (sqrt (atan2 b b)) (- b a)))) - -(FPCore (a b c d) - :name "Random Jason Timeout Test 004" - (fmod (cosh c) (log1p a))) - -(FPCore (a) - :name "Random Jason Timeout Test 006" - (fabs (fmod (atan2 (expm1 (sin (expm1 a))) (atan a)) a))) - -(FPCore (a b c) - :name "Random Jason Timeout Test 009" - (fabs (fmod c (asin (- 2.821952756469356e+184 b))))) - (FPCore (a) - :name "Random Jason Timeout Test 010" + :name "Fuzzer 001" (/ a (- (acos a)))) (FPCore (a) - :name "Random Jason Timeout Test 011" + :name "Fuzzer 002" (pow (atan (fmod a (asin a))) (* a a))) -(FPCore (a b c) - :name "Random Jason Timeout Test 012" - (acos (pow (fmod (cosh a) (* a a)) (log1p a)))) - -(FPCore (a b c d) - :name "Random Jason Timeout Test 014" - (fmod (sinh c) (- c (sqr -2.9807307601812193e+165)))) - -(FPCore (a b c) - :name "Random Jason Timeout Test 015" - (sin (pow (sqrt (atan2 b b)) (- b a)))) - -(FPCore (a b c) - :pre (and (< 0 a) (< 0 b) (< 0 c)) - :name "Area of a triangle" - (sqrt - (* - (* - (* (/ (+ (+ a b) c) 2) (- (/ (+ (+ a b) c) 2) a)) - (- (/ (+ (+ a b) c) 2) b)) - (- (/ (+ (+ a b) c) 2) c)))) - -(FPCore (n U t l Om U*) - :name "Toniolo and Linder, Equation (13)" - (sqrt - (* - (* (* 2 n) U) - (- (- t (* 2 (/ (sqr l) Om))) (* (* n (sqr (/ l Om))) (- U U*)))))) - (FPCore (x y z t a) :name "Numeric.SpecFunctions:logGammaL from math-functions-0.1.5.2" (+ (- (+ (log (+ x y)) (log z)) t) (* (- a 0.5) (log t)))) @@ -111,18 +58,18 @@ :name "Bouland and Aaronson, Equation (25)" (- (+ - (sqr (+ (sqr a) (sqr b))) - (* 4 (+ (* (sqr a) (+ 1 a)) (* (sqr b) (- 1 (* 3 a)))))) + (pow (+ (* a a) (* b b)) 2) + (* 4 (+ (* (* a a) (+ 1 a)) (* (* b b) (- 1 (* 3 a)))))) 1)) (FPCore (a b c) :name "The quadratic formula (r1)" - :target - (let ((d (- (sqr b) (* 4 a c)))) + :herbie-target + (let ((d (- (* b b) (* 4 a c)))) (let ((r1 (/ (+ (- b) (sqrt d)) (* 2 a))) (r2 (/ (- (- b) (sqrt d)) (* 2 a)))) (if (< b 0) r1 (/ c (* a r2))))) - (let ((d (- (sqr b) (* 4 a c)))) (/ (+ (- b) (sqrt d)) (* 2 a)))) + (let ((d (- (* b b) (* 4 a c)))) (/ (+ (- b) (sqrt d)) (* 2 a)))) -(FPCore (a b/2 c) +(FPCore (a b_2 c) :name "NMSE problem 3.2.1" - (let ((d (sqrt (- (sqr b/2) (* a c))))) (/ (- (- b/2) d) a))) + (let ((d (sqrt (- (* b_2 b_2) (* a c))))) (/ (- (- b_2) d) a))) diff --git a/bench/regression/web-demo.fpcore b/bench/regression/web-demo.fpcore index 99ad48549..e819f417c 100644 --- a/bench/regression/web-demo.fpcore +++ b/bench/regression/web-demo.fpcore @@ -30,16 +30,16 @@ (FPCore (x) :name "sqrt sqr" - :target + :herbie-target (if (< x 0) 2 0) (- (/ x x) (* (/ 1 x) (sqrt (* x x))))) (FPCore (a b c) :name "jeff quadratic root 1" - (let ((d (sqrt (- (sqr b) (* 4 a c))))) + (let ((d (sqrt (- (* b b) (* 4 a c))))) (if (>= b 0) (/ (- (- b) d) (* 2 a)) (/ (* 2 c) (+ (- b) d))))) (FPCore (a b c) :name "jeff quadratic root 2" - (let ((d (sqrt (- (sqr b) (* 4 a c))))) + (let ((d (sqrt (- (* b b) (* 4 a c))))) (if (>= b 0) (/ (* 2 c) (- (- b) d)) (/ (+ (- b) d) (* 2 a))))) diff --git a/bench/tutorial.fpcore b/bench/tutorial.fpcore index 8a94a649f..0d3709adb 100644 --- a/bench/tutorial.fpcore +++ b/bench/tutorial.fpcore @@ -6,7 +6,7 @@ (FPCore (x) :name "Expanding a square" - (- (sqr (+ x 1)) 1)) + (- (* (+ x 1) (+ x 1)) 1)) (FPCore (x y z) :name "Commute and associate" diff --git a/infra/.gitignore b/infra/.gitignore index 397b4a762..9d78b7872 100644 --- a/infra/.gitignore +++ b/infra/.gitignore @@ -1 +1,3 @@ *.log +*exceptions*.rkt +graphs-*/ diff --git a/infra/convert.rkt b/infra/convert.rkt index c18e81c86..1d3342938 100644 --- a/infra/convert.rkt +++ b/infra/convert.rkt @@ -75,7 +75,7 @@ ,@(translate-samplers) ,@(translate-prop '#:name ':name) ,@(translate-prop '#:expected ':herbie-expected) - ,@(translate-prop '#:target ':target (curryr search-replace vars)) + ,@(translate-prop '#:target ':herbie-target (curryr search-replace vars)) ,(search-replace body vars))) ; we assume vars and vals are of the same length diff --git a/infra/index.css b/infra/index.css index 83a775204..39565ab0e 100644 --- a/infra/index.css +++ b/infra/index.css @@ -18,8 +18,8 @@ figure { margin: 0; overflow: auto; } #graph text { text-anchor: end; } #graph .guide { stroke: rgb(60%, 60%, 60%); stroke-width: 1px; } #graph .gridline { stroke: black; stroke-width: 3px; } -#graph .arrow {pointer-events: all; stroke-width: 2px; cursor: pointer;} -#graph:hover .arrow {stroke-opacity: .6; fill-opacity: 0.6;} +#graph .arrow {pointer-events: all; stroke-width: 7px; cursor: pointer;} +#graph:hover .arrow {stroke-opacity: .7; fill-opacity: 0.7;} #graph:hover .arrow:hover { stroke-opacity: 1.0; fill-opacity: 1.0; } figure ul { margin: 0; padding: 0; list-style-type: none; list-style-position: inside; text-align: center; } figure li { padding: .5ex; display: inline-block; margin: .1em; } @@ -40,6 +40,7 @@ figure a { color: black; text-decoration: none; display: block; } #toc a:hover {text-decoration: underline; color: #295785} #reports { border-collapse: collapse; width:100%; position: relative; } +#reports tr.crash { color: red; } #reports td { text-align: right; padding: .5em; overflow: hidden; font-size: 15pt; } #reports tbody tr:hover {background-color: #e0f8d8; cursor: pointer;} #reports td:nth-child(2), #reports th:nth-child(2) { text-align: center; display: none; } diff --git a/infra/make-index.rkt b/infra/make-index.rkt index d3d3964f7..46a405588 100644 --- a/infra/make-index.rkt +++ b/infra/make-index.rkt @@ -1,31 +1,31 @@ #lang racket (require racket/runtime-path) - -(define-runtime-path report-json-path "../graphs/reports/") +(require (only-in xml write-xexpr) json) +(define-runtime-path report-json-path "../previous/") (require racket/date) (require "../src/common.rkt") (require "../src/formats/datafile.rkt") -(provide read-report-info name->timestamp) - -(define (parse-folder-name name) - (let ([name (path->string name)]) - (if (= (length (string-split name ":")) 4) - (string-split name ":") - (string-split name "-")))) +(provide directory-jsons name->timestamp) -(define name->timestamp (compose string->number first parse-folder-name)) +(define (name->timestamp path) + (define rpath (find-relative-path (simple-form-path report-json-path) path)) + (define folder (path-element->string (first (explode-path rpath)))) + (string->number + (if (string-contains? folder ":") + (first (string-split folder ":")) + folder))) -(define (read-report-info folder) - (let ([info-file (build-path report-json-path folder "results.json")]) - (if (file-exists? info-file) - (read-datafile info-file) - (match (parse-folder-name folder) - [`(,timestamp ,hostname ... ,branch ,commit) - (report-info (seconds->date (string->number timestamp)) commit branch - #f #f #f #f #f #f #f)])))) +(define (directory-jsons dir) + (reap [sow] + (let loop ([dir dir]) + (cond + [(file-exists? (build-path dir "results.json")) + (sow (find-relative-path (simple-form-path report-json-path) (simple-form-path dir)))] + [(directory-exists? dir) + (for-each loop (directory-list dir #:build? true))])))) (define (month->string i) (list-ref (string-split "Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec") (- i 1))) @@ -45,122 +45,149 @@ (cons 'fuel:2 flags) flags)) +(define *cache* (make-parameter (make-hasheq))) + +(define key-contracts + (hash string? '(date-full date-short folder commit branch hostname) + (or/c string? false) '(note) + exact-nonnegative-integer? '(date-unix tests-passed tests-available tests-crashed) + (listof string?) '(options) + (and/c real? (curryr >= 0)) '(bits-improved bits-available))) + +(define cache-row? + (apply and/c hash? + (for*/list ([(valid? keys) (in-hash key-contracts)] [key keys]) + (make-flat-contract + #:name key + #:first-order (λ (x) (and (hash-has-key? x key) (valid? (hash-ref x key)))))))) + +(define/contract (compute-row folder) + (-> path? cache-row?) + (eprintf "Reading ~a\n" folder) + (define info (read-datafile (build-path report-json-path folder "results.json"))) + (match-define (report-info date commit branch hostname seed flags points iterations bit-width note tests) info) + + (define-values (total-start total-end) + (for/fold ([start 0] [end 0]) ([row (or tests '())]) + (values + (+ start (or (table-row-start row) 0)) + (+ end (or (table-row-result row) 0))))) + + (define total-passed + (for/sum ([row (or tests '())]) + (if (member (table-row-status row) '("gt-target" "eq-target" "imp-start")) 1 0))) + (define total-available + (for/sum ([row (or tests '())]) + (if (not (equal? (table-row-status row) "ex-start")) 1 0))) + (define total-crashed + (count (compose (curry equal? "crash") table-row-status) (or tests '()))) + + (hash 'date-full (format "~a:~a on ~a" (date-hour date) (~r (date-minute date) #:min-width 2 #:pad-string "0") (date->string date)) + 'date-short (date->string/short date) + 'date-unix (date->seconds date) + 'folder (path->string folder) + 'hostname hostname + 'commit commit + 'branch branch + 'options (map ~a (get-options info)) + 'note note + 'tests-passed total-passed + 'tests-available total-available + 'tests-crashed total-crashed + 'bits-improved (- total-start total-end) + 'bits-available total-start)) + +(define (read-row folder) + (dict-ref! (*cache*) (string->symbol (path->string folder)) (λ () (compute-row folder)))) + +(define (round* x) + (cond + [(>= (abs x) (expt 10 6)) "?"] + [(>= (abs x) 10) (~a (inexact->exact (round x)))] + [else (~r x #:precision 2)])) + (define (print-rows infos #:name name) - (printf "DateBranchCollectionTestsBits\n" name name) - (printf "\n") - (for ([(folder info) (in-dict infos)]) - (match-define (report-info date commit branch seed flags points iterations bit-width note tests) info) - - (define-values (total-start total-end) - (for/fold ([start 0] [end 0]) ([row (or tests '())]) - (values - (+ start (or (table-row-start row) 0)) - (+ end (or (table-row-result row) 0))))) - - (define total-passed - (for/sum ([row (or tests '())]) - (if (member (table-row-status row) '("gt-target" "eq-target" "imp-start")) 1 0))) - (define total-available - (for/sum ([row (or tests '())]) - (if (not (equal? (table-row-status row) "ex-start")) 1 0))) - - (define (round* x) - (cond - [(>= (abs x) (expt 10 6)) - "?"] - [(>= (abs x) 10) - (~a (inexact->exact (round x)))] - [else - (~r x #:precision 2)])) - - (printf "") - (printf "" - (date-hour date) (~r (date-minute date) #:min-width 2 #:pad-string "0") - ;; TODO: Best to output a datetime field in RFC3338 format, - ;; but Racket doesn't make that easy. - (date->string date) (date->seconds date) (date->string/short date)) - (printf "~a" commit branch) - (if note - (printf "~a" (string-join (map ~a (get-options info)) " ") note) - (printf "⭐" (string-join (map ~a (get-options info)) " "))) - (if tests - (printf "~a/~a" total-passed total-available) - (printf "")) - (if (> total-start 0) - (printf "~a/~a" (round* (- total-start total-end)) (round* total-start)) - (printf "")) - (printf "»" folder) - (printf "\n")) - (printf "\n")) + `((thead ((id ,(format "reports-~a" name)) (data-branch ,name)) + (th "Date") (th "Branch") (th "Collection") (th "Tests") (th "Bits")) + (tbody + ,@(for/list ([info infos]) + (define field (curry dict-ref info)) + + `(tr ([class ,(if (> (field 'tests-crashed) 0) "crash" "")]) + ;; TODO: Best to output a datetime field in RFC3338 format, + ;; but Racket doesn't make that easy. + (td ([title ,(field 'date-full)]) + (time ([data-unix ,(~a (field 'date-unix))]) ,(field 'date-short))) + (td ([title ,(field 'commit)]) ,(field 'branch)) + (td ([title ,(string-join (field 'options) " ")] + [class ,(if (field 'note) "note" "")]) + ,(or (field 'note) "⭐")) + (td ,(if (> (field 'tests-available) 0) (format "~a/~a" (field 'tests-passed) (field 'tests-available)) "")) + (td ,(if (field 'bits-improved) (format "~a/~a" (round* (field 'bits-improved)) (round* (field 'bits-available))) "")) + (td ([title ,(format "At ~a\nOn ~a\nFlags ~a" (field 'date-full) (field 'hostname) (string-join (field 'options) " "))]) + (a ([href ,(format "./~a/report.html" (field 'folder))]) "»"))))))) (define (make-index-page) - (define dirs (directory-list report-json-path)) - - (let* ([folders - (map (λ (dir) (cons dir (read-report-info dir))) - (remove-duplicates - (sort (filter name->timestamp dirs) > #:key name->timestamp) - #:key name->timestamp))]) - - (write-file "index.html" - (printf "\n") - (printf "") - (printf "") - (printf "Herbie Reports\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - - (define branch-infos* - (sort - (group-by (compose report-info-branch cdr) folders) - > #:key (λ (x) (date->seconds (report-info-date (cdar x)))))) - - (define-values (master-info* other-infos) - (partition (λ (x) (equal? (report-info-branch (cdar x)) "master")) - branch-infos*)) - - ;; This is a hack due to the use of partition to simultaneously - ;; find the reports with branch master, and also to remove it - ;; from the list. - (define master-info (and (not (null? master-info*)) (car master-info*))) - - ; Big bold numbers - (printf "
\n") - (printf "
Reports: ~a
\n" - (length folders)) - (printf "
Branches: ~a
\n" - (length branch-infos*)) - (when master-info - (printf "
On Master: ~a
\n" - (length master-info))) - (printf "
\n") - - (printf "
    ") - (for ([rows (if master-info (cons master-info other-infos) other-infos)]) - (define branch (report-info-branch (cdar rows))) - (printf "
  • ~a
  • " branch branch)) - (printf "
") - - (printf "
") - (printf "
    ") - (printf "\n") - (printf "
      ") - (printf "\n") - (printf "
      \n") - - (printf "\n") - (when master-info - (print-rows master-info #:name "master")) - (for ([rows other-infos]) - (print-rows rows #:name (report-info-branch (cdar rows)))) - (printf "
      \n") - - (printf "\n") - (printf "\n")))) + (when (file-exists? (build-path report-json-path "index.cache")) + (define cached-info (hash-copy (call-with-input-file (build-path report-json-path "index.cache") read-json))) + (if (for/and ([(k v) (in-hash cached-info)]) (cache-row? v)) + (*cache* cached-info) + (eprintf "Ignoring cache; contains invalid values"))) + + (define dirs (directory-jsons report-json-path)) + (define folders + (map read-row (sort (filter name->timestamp dirs) > #:key name->timestamp))) + + (define branch-infos* + (sort + (group-by (curryr dict-ref 'branch) folders) + > #:key (λ (x) (dict-ref (first x) 'date-unix)))) + + (define-values (mainline-infos other-infos) + (partition (λ (x) (set-member? '("master" "develop") (dict-ref (first x) 'branch))) + branch-infos*)) + + (define last-crash + (argmax (curryr dict-ref 'date-unix) (apply append mainline-infos))) + (define since-last-crash + (/ (- (date->seconds (current-date)) (dict-ref last-crash 'date-unix)) (* 60 60 24))) + + (write-file "index.html" + (printf "\n") + (write-xexpr + `(html + (head + (meta ((charset "utf-8"))) + (title "Herbie Reports") + (link ((rel "stylesheet") (href "index.css"))) + (script ((src "http://d3js.org/d3.v3.min.js") (charset "utf-8"))) + (script ((src "regression-chart.js"))) + (script ((src "report.js")))) + (body + ((onload "index()")) + (div + ((id "large")) + (div "Reports: " (span ((class "number")) ,(~a (length folders)))) + (div "Mainline: " (span ((class "number")) ,(~a (length (apply append mainline-infos))))) + (div "Branches: " (span ((class "number")) ,(~a (length branch-infos*)))) + (div "Crash-free: " (span ((class "number")) ,(~a (inexact->exact (round since-last-crash))) "d"))) + (ul ((id "toc")) + ,@(for/list ([rows (append mainline-infos other-infos)]) + (define branch (dict-ref (first rows) 'branch)) + `(li (a ((href ,(format "#reports-~a" branch))) ,branch)))) + (figure + (ul ((id "classes"))) + (svg ((id "graph") (width "800"))) + (ul ((id "suites"))) + (script "window.addEventListener('load', function(){draw_results(d3.select('#graph'))})")) + (table + ((id "reports")) + ,@(apply + append + (for/list ([rows (append mainline-infos other-infos)]) + (print-rows rows #:name (dict-ref (first rows) 'branch))))))))) + + (call-with-output-file (build-path report-json-path "index.cache") #:exists 'replace (curry write-json (*cache*)))) (module+ main (make-index-page)) diff --git a/infra/nightly.sh b/infra/nightly.sh new file mode 100644 index 000000000..4eefd8302 --- /dev/null +++ b/infra/nightly.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +CORES=6 + +function run { + bench=$1; shift + name=$1; shift + + racket "src/herbie.rkt" report \ + --note "$name" \ + --profile \ + --threads $CORES \ + "$@" \ + "$bench" "reports/$name" + bash infra/publish.sh upload "reports/$name" +} + +function runEach { + for bench in bench/*; do + name=$(basename "$bench" .fpcore) + # add cases to skip large or misbehaving benchmarks + case $name in + haskell) ;; + random) ;; + *) run "$bench" "$name" "$@" ;; + esac + done +} + +mkdir -p reports +runEach --seed $(date "+%Y%j") "$@" diff --git a/infra/publish.sh b/infra/publish.sh index 61e1f76e3..f51215ff5 100755 --- a/infra/publish.sh +++ b/infra/publish.sh @@ -10,7 +10,7 @@ upload () { C=$(git rev-parse HEAD | sed 's/\(..........\).*/\1/') RDIR="$(date +%s):$(hostname):$B:$C" find "$DIR" -name "debug.txt" -exec gzip -f {} \; - rsync --recursive "$1" --exclude reports/ "$RHOST:$RHOSTDIR/$RDIR" + rsync --recursive "$DIR" --exclude reports/ "$RHOST:$RHOSTDIR/$RDIR" ssh "$RHOST" chmod a+rx "$RHOSTDIR/$RDIR" -R } @@ -28,12 +28,13 @@ backfill () { } download_reports () { - rsync --include 'results.json' --include '/*/' --exclude '*' \ - --recursive uwplse.org:/var/www/herbie/reports/ graphs/reports/ + rsync --recursive --checksum --inplace --ignore-existing \ + --include 'results.json' --include '*/' --exclude '*' \ + uwplse.org:/var/www/herbie/reports/ previous/ } upload_reports () { - rsync --recursive graphs/reports/ uwplse.org:/var/www/herbie/reports/ + rsync --recursive previous/ uwplse.org:/var/www/herbie/reports/ } help () { diff --git a/infra/regression-chart.js b/infra/regression-chart.js index 247080044..d4aa1bed8 100644 --- a/infra/regression-chart.js +++ b/infra/regression-chart.js @@ -107,6 +107,9 @@ function make_graph(node, data, type) { var g = bar.append("g").attr("class", "arrow"); + g.append("title") + .text(function(d) { return "At " + new Date(d.time * 1000) + "\nOn " + d.branch }); + g.append("line") .attr("stroke", function(d) { return key(d.branch) }) .attr("x1", function(d, i) { return (i + .5) * spacing }) @@ -114,28 +117,14 @@ function make_graph(node, data, type) { .attr("y1", function(d) { return height - height * d[type].total / max }) .attr("y2", function(d) { return height - height * (d[type].total - d[type].got) / max - 5 }); - g.append("polygon").attr("points", "-3,-5,3,-5,0,0") + g.append("polygon").attr("points", "-3.5,-6,3.5,-6,0,0") .attr("fill", function(d) { return key(d.branch) }) .attr("transform", function(d, i) { return "translate(" + spacing*(i + .5) + ", " + (height - height * (d[type].total - d[type].got) / max) + ")"; }); - - g.append("line") - .attr("stroke", function(d) { return key(d.branch) }) - .attr("x1", function(d, i) { return (i + .5) * spacing - 3 }) - .attr("x2", function(d, i) { return (i + .5) * spacing + 3 }) - .attr("y1", function(d) { return height - height * d[type].total / max }) - .attr("y2", function(d) { return height - height * d[type].total / max }); - - g.append("line") - .attr("stroke", function(d) { return key(d.branch) }) - .attr("x1", function(d, i) { return (i + .5) * spacing - 3 }) - .attr("x2", function(d, i) { return (i + .5) * spacing + 3 }) - .attr("y1", function(d) { return height - height * (d[type].total - d[type].got) / max }) - .attr("y2", function(d) { return height - height * (d[type].total - d[type].got) / max }); g.on("click", function(d) { - d.elt.click(); + d.elt.querySelector("a").click(); }); } @@ -163,8 +152,7 @@ function render(node, data, options, tag) { function draw_results(node) { DATA = get_data(document.getElementById("reports")); - OPTIONS = {"fuel:2": false, "reduce:regimes": true, "precision:double": true, - "rules:numerics": false}; + OPTIONS = {"rules:numerics": false}; TAG = null; NODE = node; @@ -186,7 +174,7 @@ function draw_results(node) { var type_list = document.getElementById("suites"); for (var type in used_tag) { if (!type) continue; - if (!best_type || used_tag[type] > used_tag[best_type]) best_type = type; + if ((!best_type || used_tag[type] > used_tag[best_type]) && type !== "tutorial") best_type = type; var li = document.createElement("li"); var a = document.createElement("a"); a.href = "#" + type; diff --git a/infra/results-to-csv.rkt b/infra/results-to-csv.rkt deleted file mode 100644 index fc6403ca6..000000000 --- a/infra/results-to-csv.rkt +++ /dev/null @@ -1,19 +0,0 @@ -#lang racket - -(require "../src/common.rkt") -(require "../src/reports/datafile.rkt") - -(define (results-to-csv infile outfile) - (let ([lines (read-datafile infile)]) - (write-file outfile - (for ([line lines]) - (display (car line)) - (for ([item (cdr line)]) - (display ",") - (display item)) - (newline))))) - -(command-line - #:program "results-to-csv" - #:args (infile outfile) - (results-to-csv infile outfile)) diff --git a/infra/run.sh b/infra/run.sh index 8c337d9a5..6f20e6220 100755 --- a/infra/run.sh +++ b/infra/run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# this is where herbie lives on warfa for nightlies -# you may change HERBROOT to test locally, +# This is where herbie lives on warfa for nightlies. +# You may change HERBROOT to test locally, # but do not push any changes to HERBROOT! HERBROOT="$HOME/herbie" @@ -20,12 +20,22 @@ function main { cd "$HERBROOT" git pull --quiet - make --quiet --directory="$HERBROOT/randTest" - java -classpath "$HERBROOT/randTest/" RandomTest \ - --size 5 --size-wiggle 5 \ - --nvars 1 --nvars-wiggle 3 \ - --ntests 20 \ - > "$HERBROOT/bench/random.fpcore" + COMMIT="$HERBROOT/infra/latest-commit.txt" + C=$(git rev-parse HEAD | sed 's/\(..........\).*/\1/') + if [ -f "$COMMIT" -a "$C" = "$(cat "$COMMIT")" ]; then + echo "No new commits, exiting." + exit 0 + else + echo "$C" > "$COMMIT" + echo "Latest commit updated to $C." + fi + +## make --quiet --directory="$HERBROOT/randTest" +## java -classpath "$HERBROOT/randTest/" RandomTest \ +## --size 5 --size-wiggle 5 \ +## --nvars 1 --nvars-wiggle 3 \ +## --ntests 20 \ +## > "$HERBROOT/bench/random.fpcore" # choose configs based on day of year d=$(date "+%j") @@ -37,33 +47,39 @@ function main { (current-pseudo-random-generator))") seed="${qseed:1}" # :1 removes leading quote - # toggle fuel every two days - if [ $(expr \( $d / 2 \) % 2) -eq 0 ]; then - fuel="--fuel 2" - else - fuel="--fuel 3" - fi - - # toggle regimes every other day - if [ $(expr $d % 2) -eq 0 ]; then - regime="" - else - regime="--disable reduce:regimes" - fi - - # toggle some configs every day - for prec in "" "--disable precision:double"; do - for postproc in "" "--enable reduce:post-process"; do - for num in "" "--enable rules:numerics"; do - runEach --seed "$seed" $fuel $regime $prec $postproc $num - done - done - done +## # toggle fuel every two days +## if [ $(expr \( $d / 2 \) % 2) -eq 0 ]; then +## fuel="--fuel 2" +## else +## fuel="--fuel 3" +## fi + +## # toggle regimes every other day +## if [ $(expr $d % 2) -eq 0 ]; then +## regime="" +## else +## regime="--disable reduce:regimes" +## fi + +## # toggle some configs every day +## for prec in "" "--disable precision:double"; do +## for postproc in "" "--enable reduce:post-process"; do +## for num in "" "--enable rules:numerics"; do +## runEach --seed "$seed" $fuel $regime $prec $postproc $num +## done +## done +## done + + runEach --seed "$seed" } function run { bench=$1; shift - name=$1; shift + name=$1; shift + + GRAPHS="$HERBROOT/infra/graphs-$(date +%y%m%d%H%M%S)" + mkdir -p "$GRAPHS" + cat << EOF ================================================================================ @@ -74,12 +90,12 @@ run $@ EOF time xvfb-run --auto-servernum \ - racket "$HERBROOT/src/reports/run.rkt" \ + racket "$HERBROOT/src/herbie.rkt" report \ --note "$name" \ --profile \ --threads $CORES \ "$@" \ - "$bench" + "$bench" "$GRAPHS" cat << EOF >> "$EXC" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -87,22 +103,30 @@ EOF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; EOF - cat "$HERBROOT/graphs/exceptions.rkt" >> "$EXC" - echo - echo "Evaluating extracted C" - time make \ - --quiet --directory="$HERBROOT/graphs" \ - --jobs=$CORES \ - overhead + cat "$GRAPHS/exceptions.rkt" >> "$EXC" + +## echo +## echo "Evaluating extracted C" +## time make \ +## --quiet --directory="$HERBROOT/graphs" \ +## --jobs=$CORES \ +## overhead +## echo + echo echo "Publishing to uwplse.org" - # ignore verbose rsync output and shell trace - time make \ - --quiet --directory="$HERBROOT" \ - publish 2>&1 > /dev/null | grep -v '^+' + # NOTE: the trailing slash at the end of GRAPHS is required for rsync! + time "$HERBROOT/infra/publish.sh" upload "$GRAPHS/" + + echo + echo "Rebuilding reports index" + time "$HERBROOT/infra/publish.sh" index + + rm -rf "$GRAPHS" } function runEach { + ##for bench in $HERBROOT/bench/hamming; do for bench in $HERBROOT/bench/*; do name=$(basename "$bench" .fpcore) # add cases to skip large or misbehaving benchmarks diff --git a/infra/travis.rkt b/infra/travis.rkt index 621dede3e..856b9d5b7 100644 --- a/infra/travis.rkt +++ b/infra/travis.rkt @@ -12,7 +12,7 @@ (define (run-tests . bench-dirs) (define tests (append-map load-tests bench-dirs)) (define seed (get-seed)) - (printf "Running Herbie on ~a tests...\nSeed: ~a\n" (length tests) seed) + (printf "Running Herbie on ~a tests (seed: ~a)...\n" (length tests) seed) (for/and ([test tests]) (match (get-test-result test #:seed seed) [(test-result test time prec input output pts exs @@ -45,10 +45,12 @@ #f]))) (module+ main + (define seed (random 1 (expt 2 31))) + (set-seed! seed) (command-line #:program "travis.rkt" #:once-each - [("--seed") rs "The random seed vector to use in point generation. If false (#f), a random seed is used'" + [("--seed") rs "The random seed to use in point generation. If false (#f), a random seed is used'" (define given-seed (read (open-input-string rs))) (when given-seed (set-seed! given-seed))] #:args bench-dir diff --git a/src/alternative.rkt b/src/alternative.rkt index 029855963..0fcc6b4b7 100644 --- a/src/alternative.rkt +++ b/src/alternative.rkt @@ -5,10 +5,10 @@ (require "core/matcher.rkt") (require "common.rkt") -(provide (struct-out alt-delta) (struct-out alt-event) +(provide (struct-out alt-delta) (struct-out alt-event) alternative? make-alt alt? alt-program alt-change alt-prev alt-add-event make-regime-alt - alt-apply alt-rewrite-tree alt-rewrite-expression + alt-apply alt-rewrite-expression alt-errors alt-cost alt-rewrite-rm alt-set-prev alt-initial alt-changes alt-history-length) @@ -31,6 +31,8 @@ (write (alt-program alt) port) (display ">" port))]) +(define alternative? (or/c alt-delta? alt-event?)) + (define (make-alt prog) (alt-event prog 'start '())) @@ -78,10 +80,6 @@ (loop (alt-prev cur-alt) (cons (alt-change cur-alt) acc)) acc))) -(define (alt-rewrite-tree alt #:root [root-loc '()]) - (let ([subtree (location-get root-loc (alt-program alt))]) - (map (curry alt-apply alt) (rewrite-tree subtree #:root root-loc)))) - (define (alt-rewrite-expression alt #:destruct [destruct? #f] #:root [root-loc '()]) (let ([subtree (location-get root-loc (alt-program alt))]) (map (curry alt-apply alt) diff --git a/src/bigcomplex.rkt b/src/bigcomplex.rkt new file mode 100644 index 000000000..a0bdab7be --- /dev/null +++ b/src/bigcomplex.rkt @@ -0,0 +1,93 @@ +#lang racket + +(require math/bigfloat) + +(struct bigcomplex (re im) #:transparent) + +(provide (contract-out + [struct bigcomplex ((re bigfloat?) (im bigfloat?))] + [bf-complex-add (-> bigcomplex? bigcomplex? bigcomplex?)] + [bf-complex-sub (-> bigcomplex? bigcomplex? bigcomplex?)] + [bf-complex-neg (-> bigcomplex? bigcomplex?)] + [bf-complex-mult (-> bigcomplex? bigcomplex? bigcomplex?)] + [bf-complex-conjugate (-> bigcomplex? bigcomplex?)] + [bf-complex-sqr (-> bigcomplex? bigcomplex?)] + [bf-complex-exp (-> bigcomplex? bigcomplex?)] + [bf-complex-log (-> bigcomplex? bigcomplex?)] + [bf-complex-sqrt (-> bigcomplex? bigcomplex?)] + [bf-complex-pow (-> bigcomplex? bigcomplex? bigcomplex?)] + [bf-complex-div (-> bigcomplex? bigcomplex? bigcomplex?)]) + exact+ exact- exact* exact/ exact-sqr exact-log exact-pow exact-sqrt exact-exp) + +(define (bf-complex-add x y) + (bigcomplex (bf+ (bigcomplex-re x) (bigcomplex-re y)) (bf+ (bigcomplex-im x) (bigcomplex-im y)))) + +(define (bf-complex-sub x [y #f]) + (if y + (bf-complex-add x (bf-complex-neg y)) + (bf-complex-neg x))) + +(define (bf-complex-neg x) + (bigcomplex (bf- (bigcomplex-re x)) (bf- (bigcomplex-im x)))) + +(define (bf-complex-mult x y) + (bigcomplex (bf+ (bf* (bigcomplex-re x) (bigcomplex-re y)) (bf- (bf* (bigcomplex-im x) (bigcomplex-im y)))) + (bf+ (bf* (bigcomplex-im x) (bigcomplex-re y)) (bf* (bigcomplex-re x) (bigcomplex-im y))))) + +(define (bf-complex-conjugate x) + (bigcomplex (bigcomplex-re x) (bf- (bigcomplex-im x)))) + +(define (bf-complex-sqr x) + (bf-complex-mult x x)) + +(define (bf-complex-exp x) + (match-define (bigcomplex re im) x) + (define scale (bfexp re)) + (bigcomplex (bf* scale (bfcos im)) (bf* scale (bfsin im)))) + +(define (bf-complex-log x) + (match-define (bigcomplex re im) x) + (define mag (bfhypot re im)) + (define arg (bfatan2 im re)) + (bigcomplex (bflog mag) arg)) + +(define (bf-complex-sqrt x) + (bf-complex-pow x (bigcomplex (bf 0.5) 0.bf))) + +(define (bf-complex-pow x n) + (bf-complex-exp (bf-complex-mult n (bf-complex-log x)))) + +(define (bf-complex-div x y) + (define numer (bf-complex-mult x (bf-complex-conjugate y))) + (define denom (bf-complex-mult y (bf-complex-conjugate y))) + (bigcomplex (bf/ (bigcomplex-re numer) (bigcomplex-re denom)) (bf/ (bigcomplex-im numer) (bigcomplex-re denom)))) + +(define (make-exact-fun bf-fun bf-complex-fun) + (lambda args + (match args + [(list (? bigfloat?) ...) + (apply bf-fun args)] + [(list (? bigcomplex?) ...) + (apply bf-complex-fun args)]))) + +(require (only-in racket/base [exp e])) + +(define exact+ (make-exact-fun bf+ bf-complex-add)) +(define exact- (make-exact-fun bf- bf-complex-sub)) +(define exact* (make-exact-fun bf* bf-complex-mult)) +(define exact/ (make-exact-fun bf/ bf-complex-div)) +(define exact-exp (make-exact-fun bfexp bf-complex-exp)) +(define exact-log (make-exact-fun bflog bf-complex-log)) +(define exact-pow (make-exact-fun bfexpt bf-complex-pow)) +(define exact-sqr (make-exact-fun bfsqr bf-complex-sqr)) +(define exact-sqrt (make-exact-fun bfsqrt bf-complex-sqrt)) + +(module+ test + (define (bf-complex-eq-approx bf1 bf2) + (check-equal? (bfround (bigcomplex-re bf1)) (bigcomplex-re bf2)) + (check-equal? (bfround (bigcomplex-im bf1)) (bigcomplex-im bf2))) + (require rackunit) + (check-equal? (bf-complex-mult (bigcomplex (bf 5) (bf 2)) (bigcomplex (bf 7) (bf 12))) (bigcomplex (bf 11) (bf 74))) + (check-equal? (bf-complex-div (bigcomplex (bf 5) (bf 2)) (bigcomplex (bf 7) (bf 4))) (bigcomplex (bf 43/65) (bf -6/65))) + (bf-complex-eq-approx (bf-complex-pow (bigcomplex (bf 2) (bf 3)) (bigcomplex (bf 3) (bf 0))) (bigcomplex (bf (- 46)) (bf 9))) + (bf-complex-eq-approx (bf-complex-pow (bigcomplex (bf 2) (bf 3)) (bigcomplex (bf 4) (bf 0))) (bigcomplex (bf (- 119)) (bf (- 120))))) diff --git a/src/common.rkt b/src/common.rkt index a4a2faefe..9f173d049 100644 --- a/src/common.rkt +++ b/src/common.rkt @@ -8,38 +8,69 @@ (require rackunit)) (provide *start-prog* - reap define-table first-value assert for/append - ordinary-float? =-or-nan? log2 - take-up-to flip-lists argmins argmaxs setfindf index-of + reap define-table table-ref table-set! table-remove! + first-value assert for/append + ordinary-value? =-or-nan? log2 i ([tbl (cons/c (listof (cons/c symbol? contract?)) (hash/c symbol? (listof any/c)))] + [key symbol?] + [field symbol?]) + [_ (tbl field) (dict-ref (car tbl) field)]) + (match-let ([(cons header rows) tbl]) + (for/first ([(field-name type) (in-dict header)] + [value (in-list (dict-ref rows key))] + #:when (equal? field-name field)) + value))) + +(define/contract (table-set! tbl key fields) + (->i ([tbl (cons/c (listof (cons/c symbol? contract?)) (hash/c symbol? (listof any/c)))] + [key symbol?] + [fields (tbl) + ;; Don't check value types because the contract gets pretty rough :( + (and/c dict? (λ (d) (andmap (curry dict-has-key? d) (dict-keys (car tbl)))))]) + any) + (match-let ([(cons header rows) tbl]) + (define row (for/list ([(hkey htype) (in-dict header)]) (dict-ref fields hkey))) + (dict-set! rows key row))) + +(define/contract (table-remove! tbl key) + ((cons/c (listof (cons/c symbol? contract?)) (hash/c symbol? (listof any/c))) symbol? . -> . void?) + (match-let ([(cons header rows) tbl]) + (dict-remove! rows key))) + +;; More various helpful values (define-syntax-rule (first-value expr) (call-with-values @@ -74,13 +105,19 @@ ;; Simple floating-point functions -(define (ordinary-float? x) - (and (real? x) (not (or (infinite? x) (nan? x))))) +(define (ordinary-value? x) + (match x + [(? real?) + (not (or (infinite? x) (nan? x)))] + [(? complex?) + (and (ordinary-value? (real-part x)) (ordinary-value? (imag-part x)))] + [(? boolean?) + true])) (module+ test - (check-true (ordinary-float? 2.5)) - (check-false (ordinary-float? +nan.0)) - (check-false (ordinary-float? -inf.f))) + (check-true (ordinary-value? 2.5)) + (check-false (ordinary-value? +nan.0)) + (check-false (ordinary-value? -inf.f))) (define (=-or-nan? x1 x2) (or (= x1 x2) @@ -92,6 +129,12 @@ (check-true (=-or-nan? +nan.0 -nan.f)) (check-false (=-or-nan? 2.3 +nan.f))) +(define ( epsilon (abs (- a b)))) +(define (binary-search-floats pred p1 p2 close-enough) (binary-search (lambda (a b) (if (close-enough a b) #f (/ (+ a b) 2))) pred p1 p2)) @@ -202,15 +257,6 @@ (let ([head (* (expt 2 31) (random-exp (- k 31)))]) (+ head (random (expt 2 31)))))) -(define (html-escape-unsafe err) - (string-replace (string-replace (string-replace err "&" "&") "<" "<") ">" ">")) - -(module+ test - (check-equal? (html-escape-unsafe "foo&bar") "foo&bar") - (check-equal? (html-escape-unsafe "foobar") "foo>bar") - (check-equal? (html-escape-unsafe "&foo") "&foo<bar>")) - (define (parse-flag s) (match (string-split s ":") [(list (app string->symbol category) (app string->symbol flag)) @@ -220,17 +266,48 @@ (list category flag))] [_ #f])) +(define the-seed #f) + (define (get-seed) - (pseudo-random-generator->vector - (current-pseudo-random-generator))) + (or the-seed (error "Seed is not set yet!"))) (define (set-seed! seed) "Reset the random number generator to a new seed" - (current-pseudo-random-generator - (vector->pseudo-random-generator seed))) + (set! the-seed seed) + (if (vector? seed) + (current-pseudo-random-generator + (vector->pseudo-random-generator seed)) + (random-seed seed))) ;; Common namespace for evaluation (define-namespace-anchor common-eval-ns-anchor) (define common-eval-ns (namespace-anchor->namespace common-eval-ns-anchor)) (define (common-eval expr) (eval expr common-eval-ns)) + +;; Matching support for syntax objects. + +;; Begin the match with a #` +;; Think of the #` as just like a ` match, same behavior +;; In fact, matching x with #`pat should do the same +;; as matching (syntax->datum x) with `pat +;; Inside the #`, you can use #, to bind not a value but a syntax object. + +(define-match-expander quasisyntax + (λ (stx) + (syntax-case stx (unsyntax unquote) + [(_ (unsyntax pat)) + #'pat] + [(_ (unquote pat)) + #'(app syntax-e pat)] + [(_ (pats ...)) + (let ([parts + (for/list ([pat (syntax-e #'(pats ...))]) + (syntax-case pat (unsyntax unquote ...) + [... pat] + [(unsyntax a) #'a] + [(unquote a) #'(app syntax-e a)] + [a #'(quasisyntax a)]))]) + #`(app syntax-e #,(datum->syntax stx (cons #'list parts))))] + [(_ a) + #'(app syntax-e 'a)]))) diff --git a/src/config.rkt b/src/config.rkt index 8160b7a5c..87c04414d 100644 --- a/src/config.rkt +++ b/src/config.rkt @@ -8,18 +8,20 @@ ;; Flag Stuff (define all-flags - #hash([precision . (double)] + #hash([precision . (double fallback)] + [fn . (cbrt)] ;; TODO: This is a bad way to disable functions: figure out a better one [setup . (simplify early-exit)] [generate . (rr taylor simplify)] - [reduce . (regimes taylor simplify avg-error post-process)] - [rules . (arithmetic polynomials fractions exponents trigonometry hyperbolic numerics)])) + [reduce . (regimes taylor simplify avg-error post-process binary-search branch-expressions)] + [rules . (arithmetic polynomials fractions exponents trigonometry hyperbolic numerics complex special bools branches)])) (define default-flags - #hash([precision . (double)] + #hash([precision . (double fallback)] + [fn . (cbrt)] [setup . (simplify)] [generate . (rr taylor simplify)] - [reduce . (regimes taylor simplify avg-error)] - [rules . (arithmetic polynomials fractions exponents trigonometry hyperbolic)])) + [reduce . (regimes taylor simplify avg-error binary-search branch-expressions)] + [rules . (arithmetic polynomials fractions exponents trigonometry hyperbolic complex special bools branches)])) (define (enable-flag! category flag) (define (update cat-flags) (set-add cat-flags flag)) @@ -29,7 +31,7 @@ (define (update cat-flags) (set-remove cat-flags flag)) (*flags* (dict-update (*flags*) category update))) -(define (has-flag? class flag) +(define (flag-set? class flag) (set-member? (dict-ref (*flags*) class) flag)) ; `hash-copy` returns a mutable hash, which makes `dict-update` invalid @@ -38,29 +40,25 @@ (define (changed-flags) (filter identity (for*/list ([(class flags) all-flags] [flag flags]) - (match* ((has-flag? class flag) - (parameterize ([*flags* default-flags]) (has-flag? class flag))) + (match* ((flag-set? class flag) + (parameterize ([*flags* default-flags]) (flag-set? class flag))) [(#t #t) #f] [(#f #f) #f] [(#t #f) (list 'enabled class flag)] [(#f #t) (list 'disabled class flag)])))) -(define ((flag type f) a b) - (if (has-flag? type f) a b)) - ;; Number of points to sample for evaluating program accuracy (define *num-points* (make-parameter 256)) ;; Number of iterations of the core loop for improving program accuracy -(define *num-iterations* (make-parameter 3)) +(define *num-iterations* (make-parameter 4)) ;; The step size with which arbitrary-precision precision is increased ;; DANGEROUS TO CHANGE (define *precision-step* (make-parameter 256)) -;; When doing a binary search in regime inference, -;; this is the fraction of the gap between two points that the search must reach -(define *epsilon-fraction* (/ 1 200)) +;; Maximum MPFR precision allowed during exact evaluation +(define *max-mpfr-prec* (make-parameter 10000)) ;; In periodicity analysis, ;; this is how small the period of a function must be to count as periodic @@ -71,15 +69,24 @@ (define *binary-search-test-points* (make-parameter 16)) +;; How accurate to make the binary search +(define *binary-search-accuracy* (make-parameter 48)) + ;;; About Herbie: +(define (run-command cmd) + (parameterize ([current-error-port (open-output-nowhere)]) + (string-trim (with-output-to-string (λ () (system cmd)))))) (define (git-command #:default [default ""] gitcmd . args) (if (directory-exists? ".git") - (let ([cmd (format "git ~a ~a" gitcmd (string-join args " "))]) - (or (string-trim (with-output-to-string (λ () (system cmd)))) default)) + (let* ([cmd (format "git ~a ~a" gitcmd (string-join args " "))] + [out (run-command cmd)]) + (if (equal? out "") default out)) default)) -(define *herbie-version* "1.1") +(define *herbie-version* "1.2") + +(define *hostname* (run-command "hostname")) (define *herbie-commit* (git-command "rev-parse" "HEAD" #:default *herbie-version*)) diff --git a/src/core/egraph.rkt b/src/core/egraph.rkt index e9725c66a..94c580cb9 100644 --- a/src/core/egraph.rkt +++ b/src/core/egraph.rkt @@ -55,6 +55,7 @@ (define (check-egraph-valid eg #:loc [location 'check-egraph-valid]) (let ([leader->iexprs (egraph-leader->iexprs eg)] [count (egraph-cnt eg)]) + (assert (not (hash-has-key? leader->iexprs #f))) ;; The egraphs count must be a positive integer (assert (and (integer? count) (positive? count)) #:loc location) ;; The top is a valid enode. (enode validity is verified upon creation). @@ -118,18 +119,18 @@ en) en))) +(define (mk-enode-rec! eg expr) + (match expr + [(list op args ...) + (mk-enode! eg (cons op (map (curry mk-enode-rec! eg) args)))] + [_ + (mk-enode! eg expr)])) + ;; Takes a plain mathematical expression, quoted, and returns the egraph ;; representing that expression with no expansion or saturation. (define (mk-egraph expr) - (define (expr->enode eg expr) - (if (list? expr) - (mk-enode! eg - (cons (car expr) - (map (curry expr->enode eg) - (cdr expr)))) - (mk-enode! eg expr))) (let ([eg (egraph 0 #f (make-hash) (make-hash))]) - (set-egraph-top! eg (expr->enode eg expr)) + (set-egraph-top! eg (mk-enode-rec! eg expr)) ;; This is an expensive check, but useful for debuggging. #;(check-egraph-valid eg #:loc 'constructing-egraph) eg)) @@ -334,18 +335,19 @@ (update-leader! eg old-vars leader leader*))))) (define (reduce-to-new! eg en expr) - (let* ([new-en (mk-enode! eg expr)] - [vars (enode-vars en)] - [leader (merge-egraph-nodes! eg en new-en)]) - (hash-update! (egraph-leader->iexprs eg) - leader - (λ (st) - (for/mutable-set ([expr st]) - (update-en-expr expr)))) - (let ([leader* (pack-filter! (λ (inner-en) - (equal? (enode-expr inner-en) expr)) - leader)]) - (update-leader! eg vars leader leader*)))) + (unless true + (let* ([new-en (mk-enode-rec! eg expr)] + [vars (enode-vars en)] + [leader (merge-egraph-nodes! eg en new-en)]) + (hash-update! (egraph-leader->iexprs eg) + leader + (λ (st) + (for/mutable-set ([expr st]) + (update-en-expr expr)))) + (let ([leader* (pack-filter! (λ (inner-en) + (equal? (enode-expr inner-en) (enode-expr new-en))) + leader)]) + (update-leader! eg vars leader leader*))))) ;; Draws a representation of the egraph to the output file specified ;; in the DOT format. diff --git a/src/core/enode.rkt b/src/core/enode.rkt index 042ee5b6e..387831905 100644 --- a/src/core/enode.rkt +++ b/src/core/enode.rkt @@ -1,11 +1,13 @@ #lang racket (require "../common.rkt") +(require "../syntax/syntax.rkt") +(require "../type-check.rkt") (provide new-enode enode-merge! enode-vars refresh-vars! enode-pid enode? - enode-expr + enode-expr enode-type pack-leader pack-members rule-applied? rule-applied! enode-subexpr? @@ -49,7 +51,7 @@ ;;# ;;################################################################################;; -(struct enode (expr id-code children parent depth cvars applied-rules) +(struct enode (expr id-code children parent depth cvars applied-rules type) #:mutable #:methods gen:custom-write [(define (write-proc en port mode) @@ -62,11 +64,38 @@ (define (hash2-proc en recurse-hash) (enode-id-code en))]) +; Get the type for an enode or an enode expr +(define (type-of-enode-expr expr) + (match expr + [(? real?) 'real] + [(? complex?) 'complex] + [(? constant?) (constant-info expr 'type)] + [(? variable?) 'real] + [(list op ens ...) + (define sigs (get-sigs op (length ens))) + (define argtypes + (for/list ([en ens]) + (enode-type en))) + (for/or ([sig sigs]) + (argtypes->rtype argtypes sig))])) + +(module+ test + (require rackunit) + (define x (new-enode '1 1)) + (define y (new-enode '2 2)) + (define xplusy (new-enode (list '+ x y) 3)) + (check-equal? (type-of-enode-expr (enode-expr xplusy)) 'real) + (define xc (new-enode '1+2i 1)) + (define yc (new-enode '2+3i 2)) + (define xcplusyc (new-enode (list '+ xc yc) 3)) + (check-equal? (type-of-enode-expr (enode-expr xcplusyc)) 'complex)) + + ;; Creates a new enode. Keep in mind that this is egraph-blind, ;; and it should be wrapped in an egraph function for registering ;; with the egraph on creation. (define (new-enode expr id-code) - (let ([en* (enode expr id-code '() #f 1 (set expr) (mutable-set))]) + (let ([en* (enode expr id-code '() #f 1 (set expr) (mutable-set) (type-of-enode-expr expr))]) (check-valid-enode en* #:loc 'node-creation) en*)) diff --git a/src/core/localize.rkt b/src/core/localize.rkt index 31d677f22..1984a962c 100644 --- a/src/core/localize.rkt +++ b/src/core/localize.rkt @@ -29,14 +29,14 @@ (let ([exact-ift (car (localize-on-expression ift vars cache))] [exact-iff (car (localize-on-expression iff vars cache))] [exact-cond (for/list ([(p _) (in-pcontext (*pcontext*))]) - ((eval-prog `(λ ,(map car vars) ,c) mode:bf) p))]) + ((eval-prog `(λ ,(map car vars) ,c) 'bf) p))]) (cons (for/list ([c exact-cond] [t exact-ift] [f exact-iff]) (if c t f)) (repeat 1)))] [`(,f ,args ...) (let* ([argvals (flip-lists (map (compose car (curryr localize-on-expression vars cache)) args))] - [f-exact (real-op->bigfloat-op f)] - [f-approx (real-op->float-op f)] + [f-exact (operator-info f 'bf)] + [f-approx (operator-info f 'fl)] [exact (map (curry apply f-exact) argvals)] [approx (map (compose (curry apply f-approx) (curry map ->flonum)) argvals)] [error @@ -58,9 +58,9 @@ (define locs (reap [sow] - (for ([(expr locs) (in-hash expr->loc)]) - (define err - (cdr (hash-ref! cache expr (λ () (localize-on-expression expr varmap cache))))) + (for ([(expr locs) (in-hash expr->loc)] + #:when (hash-has-key? cache expr)) + (define err (cdr (hash-ref cache expr))) (when (ormap (curry < 1) err) (for-each (compose sow (curry cons err)) locs))))) diff --git a/src/core/matcher.rkt b/src/core/matcher.rkt index 6ec63783c..28e619c4d 100644 --- a/src/core/matcher.rkt +++ b/src/core/matcher.rkt @@ -3,11 +3,12 @@ (require "../common.rkt") (require "../programs.rkt") (require "../syntax/rules.rkt") +(require "../type-check.rkt") (provide (all-from-out "../syntax/rules.rkt") pattern-substitute pattern-match - rewrite-expression-head rewrite-expression rewrite-tree + rewrite-expression-head rewrite-expression (struct-out change) change-apply changes-apply rule-rewrite) ;; Our own pattern matcher. @@ -115,8 +116,10 @@ (display ">" port))]) (define (rewrite-expression expr #:destruct [destruct? #f] #:root [root-loc '()]) + (define env (for/hash ([v (free-variables expr)]) (values v 'real))) (reap [sow] - (for ([rule (*rules*)]) + ; TODO: don't recompute the type of every expression + (for ([rule (if (equal? 'complex (type-of expr env)) (*complex-rules*) (*rules*))]) (let* ([applyer (if destruct? rule-apply-force-destructs rule-apply)] [result (applyer rule expr)]) (when result @@ -124,16 +127,17 @@ (define (rewrite-expression-head expr #:root [root-loc '()] #:depth [depth 1]) + (define env (for/hash ([v (free-variables expr)]) (values v 'real))) (define (rewriter expr ghead glen loc cdepth) ; expr _ _ _ _ -> (list (list change)) (reap (sow) - (for ([rule (*rules*)]) - (when (and (list? (rule-output rule)) - (or - (not ghead) ; Any results work for me - (and - (= (length (rule-output rule)) glen) - (eq? (car (rule-output rule)) ghead)))) + (for ([rule (if (equal? 'complex (type-of expr env)) (*complex-rules*) (*rules*))]) + (when (or + (not ghead) ; Any results work for me + (and + (list? (rule-output rule)) + (= (length (rule-output rule)) glen) + (eq? (car (rule-output rule)) ghead))) (let ([options (matcher expr (rule-input rule) loc (- cdepth 1))]) (for ([option options]) ; Each option is a list of change lists @@ -194,14 +198,6 @@ ; The #f #f mean that any output result works. It's a bit of a hack (rewriter expr #f #f (reverse root-loc) depth)) -(define (rewrite-tree expr #:root [root-loc '()]) - (reap [sow] - (let ([try-rewrites - (λ (expr loc) - (map sow (rewrite-expression expr #:root (append root-loc loc))) - expr)]) - (location-induct expr #:variable try-rewrites #:primitive try-rewrites)))) - (define (change-apply cng prog) (let ([loc (change-location cng)] [template (rule-output (change-rule cng))] diff --git a/src/core/periodicity.rkt b/src/core/periodicity.rkt index d651b85eb..5e9c761fb 100644 --- a/src/core/periodicity.rkt +++ b/src/core/periodicity.rkt @@ -18,7 +18,7 @@ (require racket/match) (require "../common.rkt") -(require (except-in "../programs.rkt" constant?)) +(require "../programs.rkt") (require "../alternative.rkt") (require "../points.rkt") (require "../syntax/rules.rkt") @@ -29,7 +29,7 @@ (provide optimize-periodicity (struct-out lp)) -(define (constant? a) (eq? (annotation-type a) 'constant)) +(define (constant-value? a) (eq? (annotation-type a) 'constant)) (define (linear? a) (eq? (annotation-type a) 'linear)) (define (periodic? a) (or (eq? (annotation-type a) 'periodic) (interesting? a))) (define (interesting? a) (eq? (annotation-type a) 'interesting)) @@ -59,14 +59,14 @@ (define (default-combine expr loc special) (cond [special special] - [(andmap constant? (cdr expr)) + [(andmap constant-value? (cdr expr)) (annotation expr loc 'constant (common-eval (cons (car expr) (map coeffs (cdr expr)))))] [(and (andmap periodic? (cdr expr)) (= 3 (length expr))) (annotation expr loc 'interesting (apply alist-merge lcm (map coeffs (filter periodic? (cdr expr)))))] - [(andmap (λ (x) (or (periodic? x) (constant? x))) (cdr expr)) + [(andmap (λ (x) (or (periodic? x) (constant-value? x))) (cdr expr)) (annotation expr loc 'periodic (apply alist-merge lcm (map coeffs (filter periodic? (cdr expr)))))] @@ -90,87 +90,89 @@ (map (curry lp-loc-cons 2) (annot->plocs (program-body (periodicity prog))))) (define (periodicity prog) - (define vars (program-variables prog)) - - (location-induct - prog - - #:constant - (λ (c loc) - ; TODO : Do something more intelligent with 'pi - (let ([val (if (rational? c) c (->flonum c))]) - (annotation val loc 'constant val))) - - #:variable - (λ (x loc) - (annotation x loc 'linear `((,x . 1)))) - - #:primitive - (λ (expr loc) - (define out (curry annotation expr loc)) - - ; Default-combine handles function-generic things - ; The match below handles special cases for various functions - (default-combine expr loc - (match expr - [`(+ ,a ,b) - (cond - [(and (constant? a) (linear? b)) - (out 'linear (coeffs b))] - [(and (linear? a) (constant? b)) - (out 'linear (coeffs a))] - [(and (linear? a) (linear? b)) - (out 'linear (alist-merge + (coeffs a) (coeffs b)))] - [else #f])] - [`(- ,a) - (cond - [(linear? a) - (out 'linear (alist-map - (coeffs a)))] - [else #f])] - [`(- ,a ,b) - (cond - [(and (constant? a) (linear? b)) - (out 'linear (coeffs b))] - [(and (linear? a) (constant? b)) - (out 'linear (coeffs a))] - [(and (linear? a) (linear? b)) - (out 'linear (alist-merge - (coeffs a) (coeffs b)))] - [else #f])] - - [`(* ,a ,b) - (cond - [(and (linear? a) (constant? b)) - (out 'linear (alist-map (curry * (coeffs b)) (coeffs a)))] - [(and (constant? a) (linear? b)) - (out 'linear (alist-map (curry * (coeffs a)) (coeffs b)))] - [else #f])] - [`(/ ,a ,b) - (cond - [(and (linear? a) (constant? b)) - (if (= 0 (coeffs b)) - (out 'constant +nan.0) - (out 'linear (alist-map (curryr / (coeffs b)) (coeffs a))))] - [else #f])] - - ; Periodic functions record their period - ; AS A MULTIPLE OF 2*PI - ; This prevents problems from round-off - [`(sin ,a) - (cond - [(linear? a) - (out 'periodic (alist-map / (coeffs a)))] - [else #f])] - [`(cos ,a) - (cond - [(linear? a) - (out 'periodic (alist-map / (coeffs a)))] - [else #f])] - [`(tan ,a) - (cond - [(linear? a) - (out 'periodic (alist-map / (coeffs a)))] - [else #f])] - [_ #f]))))) + (let loop ([prog prog] [loc '()]) + (match prog + [(list (or 'lambda 'λ) (list vars ...) body) + `(λ ,vars ,(loop body (cons 2 loc)))] + [(? constant? c) + ;; TODO : Do something more intelligent with 'PI + (let ([val (if (rational? c) c (->flonum c))]) + (annotation val (reverse loc) 'constant val))] + [(? variable? x) + (annotation x (reverse loc) 'linear `((,x . 1)))] + [(list 'if cond ift iff) + (define ift* (loop ift (cons 2 loc))) + (define iff* (loop iff (cons 3 loc))) + (if (and (equal? (annotation-type ift*) (annotation-type iff*)) + (equal? (annotation-coeffs ift*) (annotation-coeffs iff*))) + (annotation prog (reverse loc) (annotation-type ift*) (annotation-type ift*)) + (annotation prog (reverse loc) 'other #f))] + [(list op args ...) + (define expr (cons op (for/list ([idx (in-naturals 1)] [arg args]) (loop arg (cons idx loc))))) + (define out (curry annotation expr (reverse loc))) + + ;; Default-combine handles function-generic things + ;; The match below handles special cases for various functions + (default-combine expr (reverse loc) + (match expr + [`(+ ,a ,b) + (cond + [(and (constant-value? a) (linear? b)) + (out 'linear (coeffs b))] + [(and (linear? a) (constant-value? b)) + (out 'linear (coeffs a))] + [(and (linear? a) (linear? b)) + (out 'linear (alist-merge + (coeffs a) (coeffs b)))] + [else #f])] + [`(- ,a) + (cond + [(linear? a) + (out 'linear (alist-map - (coeffs a)))] + [else #f])] + [`(- ,a ,b) + (cond + [(and (constant-value? a) (linear? b)) + (out 'linear (coeffs b))] + [(and (linear? a) (constant-value? b)) + (out 'linear (coeffs a))] + [(and (linear? a) (linear? b)) + (out 'linear (alist-merge - (coeffs a) (coeffs b)))] + [else #f])] + + [`(* ,a ,b) + (cond + [(and (linear? a) (constant-value? b)) + (out 'linear (alist-map (curry * (coeffs b)) (coeffs a)))] + [(and (constant-value? a) (linear? b)) + (out 'linear (alist-map (curry * (coeffs a)) (coeffs b)))] + [else #f])] + [`(/ ,a ,b) + (cond + [(and (linear? a) (constant-value? b)) + (if (= 0 (coeffs b)) + (out 'constant +nan.0) + (out 'linear (alist-map (curryr / (coeffs b)) (coeffs a))))] + [else #f])] + + ;; Periodic functions record their period + ;; AS A MULTIPLE OF 2*PI + ;; This prevents problems from round-off + [`(sin ,a) + (cond + [(linear? a) + (out 'periodic (alist-map / (coeffs a)))] + [else #f])] + [`(cos ,a) + (cond + [(linear? a) + (out 'periodic (alist-map / (coeffs a)))] + [else #f])] + [`(tan ,a) + (cond + [(linear? a) + (out 'periodic (alist-map / (coeffs a)))] + [else #f])] + [_ #f]))]))) (define (optimize-periodicity improve-func altn) (debug "Optimizing " altn " for periodicity..." #:from 'periodicity #:depth 2) @@ -184,8 +186,7 @@ (let ([context (prepare-points-period program - (map (compose (curry * 2 pi) cdr) (lp-periods ploc)) - 'TRUE)]) + (map (compose (curry * 2 pi) cdr) (lp-periods ploc)))]) (parameterize ([*pcontext* context]) (improve-func (make-alt program))))))) plocs)] @@ -211,6 +212,6 @@ (let loop ([cur-body (program-body prog)]) (match cur-body [`(if ,cond ,a ,b) - `(if ,(expression-induct cond (program-variables prog) #:variable (curryr symbol-mod periods)) + `(if ,(replace-leaves cond #:variable (curryr symbol-mod periods)) ,(loop a) ,(loop b))] [_ cur-body]))) diff --git a/src/core/reduce.rkt b/src/core/reduce.rkt index b8c2168e2..8e3c27559 100755 --- a/src/core/reduce.rkt +++ b/src/core/reduce.rkt @@ -1,7 +1,8 @@ #lang racket (require "../common.rkt" "../programs.rkt" "matcher.rkt" - "../function-definitions.rkt" "../syntax/rules.rkt" "../syntax/syntax.rkt") + "../function-definitions.rkt" "../syntax/rules.rkt" "../syntax/syntax.rkt" + "../type-check.rkt") (provide simplify) @@ -13,6 +14,16 @@ (map rule-input (filter (λ (rule) (variable? (rule-output rule))) (*rules*)))) (define (simplify expr) + (define expr* expr) + (let loop ([expr expr]) + (match expr + [`(pow ,base ,(? real?)) + (when (equal? (type-of base (for/hash ([var (free-variables base)]) (values var 'real))) 'complex) + (error "Bad pow!!!" expr*))] + [(list f args ...) + (for-each loop args)] + [_ 'ok])) + (let ([simpl (simplify* expr)]) (debug #:from 'backup-simplify "Simplify" expr "into" simpl) simpl)) @@ -260,8 +271,9 @@ (match term [`(1 . ,x) x] [`(-1 . ,x) `(/ 1 ,x)] - [`(2 . ,x) `(sqr ,x)] - [`(-2 . ,x) `(/ 1 (sqr ,x))] [`(1/2 . ,x) `(sqrt ,x)] [`(-1/2 . ,x) `(/ 1 (sqrt ,x))] - [`(,power . ,x) `(pow ,x ,power)])) + [`(,power . ,x) + (match (type-of x (for/hash ([var (free-variables x)]) (values var 'real))) + ['real `(pow ,x ,power)] + ['complex `(pow ,x (complex ,power 0))])])) diff --git a/src/core/regimes.rkt b/src/core/regimes.rkt index 7dc2f5a2a..72ec96bae 100644 --- a/src/core/regimes.rkt +++ b/src/core/regimes.rkt @@ -7,9 +7,9 @@ (require "../points.rkt") (require "../float.rkt") (require "../syntax/syntax.rkt") -(require "../syntax/distributions.rkt") (require "matcher.rkt") (require "localize.rkt") +(require "../type-check.rkt") (module+ test (require rackunit)) @@ -19,16 +19,13 @@ (define (infer-splitpoints alts [axis #f]) (match alts [(list alt) - (list (list (sp 0 0 +inf.0)) (list alt))] + (list (list (sp 0 0 +nan.0)) (list alt))] [_ (debug "Finding splitpoints for:" alts #:from 'regime-changes #:depth 2) (define options (map (curry option-on-expr alts) (if axis (list axis) (exprs-to-branch-on alts)))) - (define options* - (for/list ([option options] #:unless (check-duplicates (map sp-point (option-splitpoints option)))) - option)) - (define best-option (argmin (compose errors-score option-errors) options*)) + (define best-option (argmin (compose errors-score option-errors) options)) (define splitpoints (option-splitpoints best-option)) (define altns (used-alts splitpoints alts)) (define splitpoints* (coerce-indices splitpoints)) @@ -43,53 +40,29 @@ (display ">" port))]) (define (exprs-to-branch-on alts) - (define critexpr (critical-subexpression (*start-prog*))) - (define vars (program-variables (alt-program (car alts)))) - - (if critexpr - (cons critexpr vars) - vars)) - -(define (critical-subexpression prog) - (define (loc-children loc subexpr) - (map (compose (curry append loc) - list) - (range 1 (length subexpr)))) - (define (all-equal? items) - (if (< (length items) 2) #t - (and (equal? (car items) (cadr items)) (all-equal? (cdr items))))) - (define (critical-child expr) - (let ([var-locs - (let get-vars ([subexpr expr] - [cur-loc '()]) - (cond [(list? subexpr) - (append-map get-vars (cdr subexpr) - (loc-children cur-loc subexpr))] - [(constant? subexpr) - '()] - [(variable? subexpr) - (list (cons subexpr cur-loc))]))]) - (cond [(null? var-locs) #f] - [(all-equal? (map car var-locs)) - (caar var-locs)] - [#t - (let get-subexpr ([subexpr expr] [vlocs var-locs]) - (cond [(all-equal? (map cadr vlocs)) - (get-subexpr (if (= 1 (cadar vlocs)) (cadr subexpr) (caddr subexpr)) - (for/list ([vloc vlocs]) - (cons (car vloc) (cddr vloc))))] - [#t subexpr]))]))) - (let* ([locs (localize-error prog)]) - (if (null? locs) - #f - (critical-child (location-get (car locs) prog))))) - -(define basic-point-search (curry binary-search (λ (p1 p2) - (if (for/and ([val1 p1] [val2 p2]) - (> *epsilon-fraction* (abs (- val1 val2)))) - p1 - (for/list ([val1 p1] [val2 p2]) - (/ (+ val1 val2) 2)))))) + (if (flag-set? 'reduce 'branch-expressions) + (let ([alt-critexprs (for/list ([alt alts]) + (all-critical-subexpressions (alt-program alt)))] + [critexprs (all-critical-subexpressions (*start-prog*))]) + (remove-duplicates (foldr append '() (cons critexprs alt-critexprs)))) + (program-variables (*start-prog*)))) + +;; Requires that expr is a λ expression +(define (critical-subexpression? expr subexpr) + (define crit-vars (free-variables subexpr)) + (define replaced-expr (replace-expression expr subexpr 1)) + (define non-crit-vars (free-variables replaced-expr)) + (set-disjoint? crit-vars non-crit-vars)) + +;; Requires that prog is a λ expression +(define (all-critical-subexpressions prog) + (define (subexprs-in-expr expr) + (cons expr (if (list? expr) (append-map subexprs-in-expr (cdr expr)) '()))) + (define prog-body (location-get (list 2) prog)) + (for/list ([expr (remove-duplicates (subexprs-in-expr prog-body))] + #:when (and (not (null? (free-variables expr))) + (critical-subexpression? prog-body expr))) + expr)) (define (used-alts splitpoints all-alts) (let ([used-indices (remove-duplicates (map sp-cidx splitpoints))]) @@ -108,58 +81,96 @@ splitpoints))) (define (option-on-expr alts expr) - (match-let* ([vars (program-variables (*start-prog*))] - [`(,pts ,exs) (sort-context-on-expr (*pcontext*) expr vars)]) - (let* ([err-lsts (parameterize ([*pcontext* (mk-pcontext pts exs)]) - (map alt-errors alts))] - [bit-err-lsts (map (curry map ulps->bits) err-lsts)] - [merged-err-lsts (map (curry merge-err-lsts pts) bit-err-lsts)] - [split-indices (err-lsts->split-indices merged-err-lsts)] - [split-points (sindices->spoints (remove-duplicates pts) expr alts split-indices)]) - (option split-points (pick-errors split-points pts err-lsts vars))))) + (define vars (program-variables (*start-prog*))) + (match-define (list pts exs) (sort-context-on-expr (*pcontext*) expr vars)) + (define splitvals (map (eval-prog `(λ ,vars ,expr) 'fl) pts)) + (define can-split? (append (list #f) (for/list ([val (cdr splitvals)] [prev splitvals]) (< prev val)))) + (define err-lsts + (parameterize ([*pcontext* (mk-pcontext pts exs)]) (map alt-errors alts))) + (define bit-err-lsts (map (curry map ulps->bits) err-lsts)) + (define merged-err-lsts (map (curry merge-err-lsts pts) bit-err-lsts)) + (define split-indices (err-lsts->split-indices merged-err-lsts can-split?)) + (for ([pidx (map si-pidx (drop-right split-indices 1))]) + (assert (> pidx 0)) + (assert (list-ref can-split? pidx))) + (define split-points (sindices->spoints pts expr alts split-indices)) + + (assert (set=? (remove-duplicates (map (point->alt split-points) pts)) + (map sp-cidx split-points))) + + (option split-points (pick-errors split-points pts err-lsts))) + +(module+ test + (parameterize ([*start-prog* '(λ (x) 1)] + [*pcontext* (mk-pcontext '((0.5) (4.0)) '(1.0 1.0))]) + (define alts (map (λ (body) (make-alt `(λ (x) ,body))) (list '(fmin x 1) '(fmax x 1)))) + + ;; This is a basic sanity test + (check (λ (x y) (equal? (map sp-cidx (option-splitpoints x)) y)) + (option-on-expr alts 'x) + '(1 0)) + + ;; This test ensures we handle equal points correctly. All points + ;; are equal along the `1` axis, so we should only get one + ;; splitpoint (the second, since it is better at the further point). + (check (λ (x y) (equal? (map sp-cidx (option-splitpoints x)) y)) + (option-on-expr alts '1) + '(0)) + + (check (λ (x y) (equal? (map sp-cidx (option-splitpoints x)) y)) + (option-on-expr alts '(if (== x 0.5) 1 +nan.0)) + '(0)))) + +;; (pred p1) and (not (pred p2)) +(define (binary-search-floats pred p1 p2) + (let ([midpoint (midpoint-float p1 p2)]) + (cond [(< (bit-difference p1 p2) 48) midpoint] + [(pred midpoint) (binary-search-floats pred midpoint p2)] + [else (binary-search-floats pred p1 midpoint)]))) ;; Accepts a list of sindices in one indexed form and returns the -;; proper splitpoints in floath form. +;; proper splitpoints in float form. A crucial constraint is that the +;; float form always come from the range [f(idx1), f(idx2)). If the +;; float form of a split is f(idx2), or entirely outside that range, +;; problems may arise. (define (sindices->spoints points expr alts sindices) - (define (eval-on-pt pt) - (let* ([expr-prog `(λ ,(program-variables (alt-program (car alts))) - ,expr)] - [val-float ((eval-prog expr-prog mode:fl) pt)]) - (if (ordinary-float? val-float) val-float - ((eval-prog expr-prog mode:bf) pt)))) + (for ([alt alts]) + (assert + (set-empty? (set-intersect (free-variables expr) + (free-variables (replace-expression (alt-program alt) expr 0)))) + #:extra-info (cons expr alt))) + + (define eval-expr + (eval-prog `(λ ,(program-variables (alt-program (car alts))) ,expr) 'fl)) (define (sidx->spoint sidx next-sidx) (let* ([alt1 (list-ref alts (si-cidx sidx))] [alt2 (list-ref alts (si-cidx next-sidx))] - [p1 (eval-on-pt (list-ref points (si-pidx sidx)))] - [p2 (eval-on-pt (list-ref points (sub1 (si-pidx sidx))))] - [eps (* (- p1 p2) *epsilon-fraction*)] + [p1 (eval-expr (list-ref points (si-pidx sidx)))] + [p2 (eval-expr (list-ref points (sub1 (si-pidx sidx))))] [pred (λ (v) - (let* ([start-prog* (replace-subexpr (*start-prog*) expr v)] - [prog1* (replace-subexpr (alt-program alt1) expr v)] - [prog2* (replace-subexpr (alt-program alt2) expr v)] + (let* ([start-prog* (replace-expression (*start-prog*) expr v)] + [prog1* (replace-expression (alt-program alt1) expr v)] + [prog2* (replace-expression (alt-program alt2) expr v)] [context (parameterize ([*num-points* (*binary-search-test-points*)]) - (prepare-points start-prog* (map (curryr cons (eval-sampler 'default)) - (program-variables start-prog*)) - 'TRUE))]) + (prepare-points start-prog* 'TRUE))]) (< (errors-score (errors prog1* context)) (errors-score (errors prog2* context)))))]) (debug #:from 'regimes "searching between" p1 "and" p2 "on" expr) - (sp (si-cidx sidx) expr (binary-search-floats pred p1 p2 eps)))) - + (sp (si-cidx sidx) expr (binary-search-floats pred p2 p1)))) (append - (if ((flag 'reduce 'binary-search) #t #f) + (if (flag-set? 'reduce 'binary-search) (map sidx->spoint (take sindices (sub1 (length sindices))) (drop sindices 1)) (for/list ([sindex (take sindices (sub1 (length sindices)))]) - (sp (si-cidx sindex) expr (eval-on-pt (list-ref points (si-pidx sindex)))))) + (sp (si-cidx sindex) expr (eval-expr (list-ref points (- (si-pidx sindex) 1)))))) (list (let ([last-sidx (list-ref sindices (sub1 (length sindices)))]) (sp (si-cidx last-sidx) expr - +inf.0))))) + +nan.0))))) (define (merge-err-lsts pts errs) (let loop ([pt (car pts)] [pts (cdr pts)] [err (car errs)] [errs (cdr errs)]) @@ -174,23 +185,23 @@ point (range (length point)))) -(define (pick-errors splitpoints pts err-lsts variables) - (reverse - (first-value - (for/fold ([acc '()] [rest-splits splitpoints]) - ([pt (in-list pts)] - [errs (flip-lists err-lsts)]) - (let* ([expr-prog `(λ ,variables ,(sp-bexpr (car rest-splits)))] - [float-val ((eval-prog expr-prog mode:fl) pt)] - [pt-val (if (ordinary-float? float-val) float-val - ((eval-prog expr-prog mode:bf) pt))]) - (if (or (<= pt-val (sp-point (car rest-splits))) - (and (null? (cdr rest-splits)) (nan? pt-val))) - (if (nan? pt-val) (error "wat") - (values (cons (list-ref errs (sp-cidx (car rest-splits))) - acc) - rest-splits)) - (values acc (cdr rest-splits)))))))) +(define (point->alt splitpoints) + (assert (all-equal? (map sp-bexpr splitpoints))) + (assert (nan? (sp-point (last splitpoints)))) + (define expr `(λ ,(program-variables (*start-prog*)) ,(sp-bexpr (car splitpoints)))) + (define prog (eval-prog expr 'fl)) + + (λ (pt) + (define val (prog pt)) + (for/first ([right splitpoints] + #:when (or (nan? (sp-point right)) (<= val (sp-point right)))) + ;; Note that the last splitpoint has an sp-point of +nan.0, so we always find one + (sp-cidx right)))) + +(define (pick-errors splitpoints pts err-lsts) + (define which-alt (point->alt splitpoints)) + (for/list ([pt pts] [errs (flip-lists err-lsts)]) + (list-ref errs (which-alt pt)))) (define (with-entry idx lst item) (if (= idx 0) @@ -221,11 +232,11 @@ ;; Struct representing a candidate set of splitpoints that we are considering. ;; cost = The total error in the region to the left of our rightmost splitpoint -;; splitpoints = The splitpoints we are considering in this candidate. -(struct cse (cost splitpoints) #:transparent) +;; indices = The si's we are considering in this candidate. +(struct cse (cost indices) #:transparent) ;; Given error-lsts, returns a list of sp objects representing where the optimal splitpoints are. -(define (err-lsts->split-indices err-lsts) +(define (err-lsts->split-indices err-lsts can-split-lst) ;; We have num-candidates candidates, each of whom has error lists of length num-points. ;; We keep track of the partial sums of the error lists so that we can easily find the cost of regions. (define num-candidates (length err-lsts)) @@ -233,6 +244,7 @@ (define min-weight num-points) (define psums (map (compose partial-sum list->vector) err-lsts)) + (define can-split? (curry vector-ref (list->vector can-split-lst))) ;; Our intermediary data is a list of cse's, ;; where each cse represents the optimal splitindices after however many passes @@ -244,7 +256,8 @@ ;; We take the CSE corresponding to the best choice of previous split point. ;; The default, not making a new split-point, gets a bonus of min-weight (let ([acost (- (cse-cost point-entry) min-weight)] [aest point-entry]) - (for ([prev-split-idx (in-naturals)] [prev-entry (in-list (take sp-prev point-idx))]) + (for ([prev-split-idx (in-naturals)] [prev-entry (in-list (take sp-prev point-idx))] + #:when (can-split? (si-pidx (car (cse-indices prev-entry))))) ;; For each previous split point, we need the best candidate to fill the new regime (let ([best #f] [bcost #f]) (for ([cidx (in-naturals)] [psum (in-list psums)]) @@ -253,10 +266,10 @@ (when (or (not best) (< cost bcost)) (set! bcost cost) (set! best cidx)))) - (when (< (+ (cse-cost prev-entry) bcost) acost) + (when (and (< (+ (cse-cost prev-entry) bcost) acost)) (set! acost (+ (cse-cost prev-entry) bcost)) (set! aest (cse acost (cons (si best (+ point-idx 1)) - (cse-splitpoints prev-entry))))))) + (cse-indices prev-entry))))))) aest))) ;; We get the initial set of cse's by, at every point-index, @@ -268,10 +281,9 @@ ;; Consider all the candidates we could put in this region (map (λ (cand-idx cand-psums) (let ([cost (vector-ref cand-psums point-idx)]) - (cse cost - (list (si cand-idx (add1 point-idx)))))) - (range num-candidates) - psums)))) + (cse cost (list (si cand-idx (+ point-idx 1)))))) + (range num-candidates) + psums)))) ;; We get the final splitpoints by applying add-splitpoints as many times as we want (define final @@ -282,33 +294,23 @@ (loop next))))) ;; Extract the splitpoints from our data structure, and reverse it. - (reverse (cse-splitpoints (last final)))) + (reverse (cse-indices (last final)))) (define (splitpoints->point-preds splitpoints num-alts) - (let* ([expr (sp-bexpr (car splitpoints))] - [variables (program-variables (*start-prog*))] - [intervals (map cons (cons #f (drop-right splitpoints 1)) - splitpoints)]) - (for/list ([i (in-range num-alts)]) - (let ([p-intervals (filter (λ (interval) (= i (sp-cidx (cdr interval)))) intervals)]) - (debug #:from 'splitpoints "intervals are: " p-intervals) - (λ (p) - (let ([expr-val ((eval-prog `(λ ,variables ,expr) mode:fl) p)]) - (for/or ([point-interval p-intervals]) - (let ([lower-bound (if (car point-interval) (sp-point (car point-interval)) #f)] - [upper-bound (sp-point (cdr point-interval))]) - (or (and (nan? expr-val) (= i (- num-alts 1))) - (and (or (not lower-bound) (lower-bound . < . expr-val)) - (expr-val . <= . upper-bound))))))))))) + (define which-alt (point->alt splitpoints)) + (for/list ([i (in-range num-alts)]) + (λ (pt) (equal? (which-alt pt) i)))) (module+ test (parameterize ([*start-prog* '(λ (x y) (/ x y))]) (define sps (list (sp 0 '(/ y x) -inf.0) (sp 2 '(/ y x) 0.0) - (sp 1 '(/ y x) +inf.0))) + (sp 0 '(/ y x) +inf.0) + (sp 1 '(/ y x) +nan.0))) (match-define (list p0? p1? p2?) (splitpoints->point-preds sps 3)) (check-true (p0? '(0 -1))) (check-true (p2? '(-1 1))) - (check-true (p1? '(+1 1))))) + (check-true (p0? '(+1 1))) + (check-true (p1? '(0 0))))) diff --git a/src/core/simplify.rkt b/src/core/simplify.rkt index dc13670a0..c54f58574 100644 --- a/src/core/simplify.rkt +++ b/src/core/simplify.rkt @@ -3,6 +3,7 @@ (require "../common.rkt") (require "../alternative.rkt") (require "../programs.rkt") +(require "../syntax/syntax.rkt") (require "../syntax/rules.rkt") (require "egraph.rkt") (require "ematch.rkt") @@ -13,6 +14,8 @@ (provide simplify-expr simplify *max-egraph-iters*) (provide (all-defined-out) (all-from-out "egraph.rkt" "../syntax/rules.rkt" "ematch.rkt")) +(module+ test (require rackunit)) + ;;################################################################################;; ;;# One module to rule them all, the great simplify. This makes use of the other ;;# modules in this directory to simplify an expression as much as possible without @@ -30,17 +33,19 @@ (define *max-egraph-iters* (make-parameter 6)) (define *node-limit* (make-parameter 500)) -(define (make-simplify-change program loc replacement) +(define/contract (make-simplify-change program loc replacement) + (-> expr? location? expr? change?) (change (rule 'simplify (location-get loc program) replacement) loc (for/list ([var (program-variables program)]) (cons var var)))) -(define (simplify altn) +(define/contract (simplify altn #:rules [rls (*simplify-rules*)]) + (->* (alternative?) (#:rules (listof rule?)) (listof change?)) (define prog (alt-program altn)) (cond - [(or (not (alt-change altn)) (null? (change-location (alt-change altn)))) - (define prog* (simplify-expr (program-body prog))) + [(not (alt-delta? altn)) + (define prog* (simplify-expr (program-body prog) #:rules rls)) (if ((num-nodes (program-body prog)) . > . (num-nodes prog*)) (list (make-simplify-change prog '(2) prog*)) '())] @@ -60,17 +65,22 @@ (reap [sow] (for ([pos (in-naturals 1)] [arg (cdr expr)] [arg-pattern (cdr pattern)]) (when (and (list? arg-pattern) (list? arg)) - (define arg* (simplify-expr arg)) + (define arg* (simplify-expr arg #:rules rls)) (debug #:from 'simplify #:tag 'exit (format "Simplified to ~a" arg*)) (when ((num-nodes arg) . > . (num-nodes arg*)) ; Simpler (sow (make-simplify-change prog (append loc (list pos)) arg*))))))])])) -(define (simplify-expr expr) +(define/contract (simplify-fp-safe altn) + (-> alternative? (listof change?)) + (simplify altn #:rules (*fp-safe-simplify-rules*))) + +(define/contract (simplify-expr expr #:rules rls) + (-> expr? #:rules (listof rule?) expr?) (debug #:from 'simplify #:tag 'enter (format "Simplifying ~a" expr)) (if (has-nan? expr) +nan.0 (let* ([iters (min (*max-egraph-iters*) (iters-needed expr))] [eg (mk-egraph expr)]) - (iterate-egraph! eg iters) + (iterate-egraph! eg iters #:rules rls) (define out (extract-smallest eg)) (debug #:from 'simplify #:tag 'exit (format "Simplified to ~a" out)) out))) @@ -114,7 +124,8 @@ (define (find-matches ens) (filter (negate null?) (for*/list ([rl rls] - [en ens]) + [en ens] + #:when (rule-valid-at-type? rl (enode-type en))) (if (rule-applied? en rl) '() (let ([bindings (match-e (rule-input rl) en)]) (if (null? bindings) '() @@ -130,16 +141,20 @@ ;; changed. While it may be aggressive to ;; invalidate any change in bindings, it seems like ;; the right thing to do for now. - [en (pack-leader en)]) - (when (equal? (match-e (rule-input rl) en) bindings) + [en (pack-leader en)] + [bindings* (match-e (rule-input rl) en)] + [applied #f]) ;; Apply the match for each binding. - (for ([binding bindings]) - (merge-egraph-nodes! eg en (substitute-e eg (rule-output rl) binding))) - ;; Prune the enode if we can. - (try-prune-enode en) - ;; Mark this node as having this rule applied so that we don't try - ;; to apply it again. - (rule-applied! en rl)))) + (for ([binding bindings] + #:when (set-member? bindings* binding)) + (merge-egraph-nodes! eg en (substitute-e eg (rule-output rl) binding)) + (set! applied #t)) + (when applied + ;; Prune the enode if we can. + (try-prune-enode en) + ;; Mark this node as having this rule applied so that we don't try + ;; to apply it again. + (rule-applied! en rl)))) (define (try-prune-enode en) ;; If one of the variations of the enode is a single variable or ;; constant, reduce to that. @@ -148,8 +163,8 @@ ;; prune it away. Loops in the egraph coorespond to identity ;; functions. #;(elim-enode-loops! eg en)) - (let ([matches (find-matches (egraph-leaders eg))]) - (for-each apply-match matches)) + (for ([m (find-matches (egraph-leaders eg))]) + (apply-match m)) (map-enodes (curry set-precompute! eg) eg)) (define-syntax-rule (matches? expr pattern) @@ -157,7 +172,26 @@ [pattern #t] [_ #f])) +(define (exact-value? type val) + (match type + ['real (exact? val)] + ['complex (exact? val)] + ['boolean true])) + +(define/match (val-of-type type val) + [('real (? real?)) true] + [('complex (? complex?)) true] + [('boolean (? boolean?)) true] + [(_ _) false]) + +(define (val-to-type type val) + (match type + ['real val] + ['complex `(complex ,(real-part val) ,(imag-part val))] + ['boolean (if val 'TRUE 'FALSE)])) + (define (set-precompute! eg en) + (define type (enode-type en)) (for ([var (enode-vars en)]) (when (list? var) (let ([constexpr @@ -169,8 +203,8 @@ (not (matches? constexpr `(/ 0))) (andmap real? (cdr constexpr))) (let ([res (eval-const-expr constexpr)]) - (when (and (ordinary-float? res) (exact? res)) - (reduce-to-new! eg en res)))))))) + (when (and (val-of-type type res) (exact-value? type res)) + (reduce-to-new! eg en (val-to-type type res))))))))) (define (hash-set*+ hash assocs) (for/fold ([h hash]) ([assoc assocs]) @@ -209,3 +243,32 @@ [((length todo-ens*) . = . (length todo-ens)) (error "failed to extract: infinite loop.")] [#t (loop todo-ens* ens->exprs*)])))) + +(module+ test + (define test-exprs + #hash([1 . 1] + [0 . 0] + [(+ 1 0) . 1] + #;[(+ 1 5) . 6] + [(+ x 0) . x] + [(- x 0) . x] + [(* x 1) . x] + [(/ x 1) . x] + #;[(- (* 1 x) (* (+ x 1) 1)) . -1] + [(- (+ x 1) x) . 1] + [(- (+ x 1) 1) . x] + [(/ (* x 3) x) . 3] + [(- (* (sqrt (+ x 1)) (sqrt (+ x 1))) + (* (sqrt x) (sqrt x))) . 1] + [(re (complex a b)) . a])) + + (for ([(original target) test-exprs]) + (with-check-info (['original original]) + (check-equal? (simplify-expr original #:rules (*simplify-rules*)) target))) + + (define no-crash-exprs + '((exp (/ (/ (* (* c a) 4) (- (- b) (sqrt (- (* b b) (* 4 (* a c)))))) (* 2 a))))) + + (for ([expr no-crash-exprs]) + (with-check-info (['original expr]) + (check-not-exn (λ () (simplify-expr expr #:rules (*simplify-rules*))))))) diff --git a/src/core/taylor.rkt b/src/core/taylor.rkt index d1b5371b1..63a8a675e 100644 --- a/src/core/taylor.rkt +++ b/src/core/taylor.rkt @@ -38,7 +38,8 @@ ; This is the memoized expansion-taking. ; The argument, `coeffs`, is the "uncorrected" degrees of the terms--`offsets` is not subtracted. - (define (get-taylor coeffs) + (define/contract (get-taylor coeffs) + (-> (listof exact-nonnegative-integer?) any/c) (hash-ref! taylor-cache coeffs (λ () (let* ([oc (get-taylor (cdr coeffs))] @@ -51,7 +52,8 @@ ; Given some uncorrected degrees, this gets you an offset to apply. ; The corrected degrees for uncorrected `coeffs` are (map - coeffs (get-offset coeffs)) - (define (get-offset coeffs) + (define/contract (get-offset coeffs) + (-> (listof exact-nonnegative-integer?) any/c) (if (null? coeffs) (car (get-taylor '())) (cons (car (get-taylor (cdr coeffs))) (get-offset (cdr coeffs))))) @@ -60,6 +62,7 @@ (define get-coeffs-hash (make-hash)) (define (get-coeffs expts) + (-> (listof exact-nonnegative-integer?) any/c) (hash-ref! get-coeffs-hash expts (λ () (if (null? expts) @@ -89,7 +92,8 @@ [coeffs (get-coeffs expts)]) (if (not coeffs) (loop empty res (+ 1 i)) - (let ([coeff (get-taylor coeffs)]) + (let ([coeff (for/fold ([coeff (get-taylor coeffs)]) ([var vars] [tform tforms]) + (replace-expression coeff var ((cdr tform) var)))]) (if (equal? coeff 0) (loop (+ empty 1) res (+ 1 i)) (loop 0 (cons (make-term coeff @@ -116,9 +120,7 @@ (cond [(equal? power 0) 1] [(equal? power 1) var] - [(equal? power 2) `(sqr ,var)] [(equal? power -1) `(/ 1 ,var)] - [(equal? power -2) `(/ 1 (sqr ,var))] [(positive? power) `(pow ,var ,power)] [(negative? power) `(pow ,var ,power)])) @@ -182,8 +184,6 @@ (apply taylor-add ((curry taylor var) arg) (map (compose taylor-negate (curry taylor var)) args))] [`(* ,left ,right) (taylor-mult (taylor var left) (taylor var right))] - [`(/ ,arg) - (taylor-invert (taylor var arg))] [`(/ 1 ,arg) (taylor-invert (taylor var arg))] [`(/ ,num ,den) @@ -238,7 +238,7 @@ (simplify `(+ (* (- ,(car arg*)) (log ,var)) ,((cdr rest) 0))) ((cdr rest) n))))))] - [`(pow ,(? (curry equal? var)) ,(? integer? power)) + [`(pow ,(? (curry equal? var)) ,(? exact-integer? power)) (cons (- power) (λ (n) (if (= n 0) 1 0)))] [`(pow ,base ,power) (taylor var `(exp (* ,power (log ,base))))] @@ -358,7 +358,7 @@ (simplify (cond [(even? n) - `(/ (- ,(coeffs* n) (sqr ,(f (/ n 2))) + `(/ (- ,(coeffs* n) (pow ,(f (/ n 2)) 2) (+ ,@(for/list ([k (in-naturals 1)] #:break (>= k (- n k))) `(* 2 (* ,(f k) ,(f (- n k))))))) (* 2 ,(f 0)))] @@ -493,3 +493,7 @@ `(pow (* ,(factorial i) ,(coeffs i)) ,p)))) (pow ,(coeffs 0) ,(- k))))]))) ,(factorial n)))))))))) + +(module+ test + (require rackunit) + (check-pred exact-integer? (car (taylor 'x '(pow x 1.0))))) diff --git a/src/errors.rkt b/src/errors.rkt index 99b75a567..ae34e000d 100644 --- a/src/errors.rkt +++ b/src/errors.rkt @@ -1,7 +1,7 @@ #lang racket (require "config.rkt") (provide raise-herbie-error raise-herbie-syntax-error - herbie-error->string + herbie-error->string herbie-error-url (struct-out exn:fail:user:herbie) (struct-out exn:fail:user:herbie:syntax)) @@ -19,6 +19,10 @@ (raise (make-exn:fail:user:herbie:syntax (apply format message args) (current-continuation-marks) url locations))) +(define (herbie-error-url exn) + (format "https://herbie.uwplse.org/doc/~a/~a" + *herbie-version* (exn:fail:user:herbie-url exn))) + (define (herbie-error->string err) (with-output-to-string (λ () diff --git a/src/float.rkt b/src/float.rkt index 02d78b8b4..9cbb993c3 100644 --- a/src/float.rkt +++ b/src/float.rkt @@ -4,7 +4,7 @@ (require "config.rkt") (require "common.rkt") -(provide ulp-difference *bit-width* ulps->bits bit-difference) +(provide midpoint-float ulp-difference *bit-width* ulps->bits bit-difference sample-float sample-double) (define (single-flonum->bit-field x) (integer-bytes->integer (real->floating-point-bytes x 4) #f)) @@ -17,9 +17,30 @@ (- (single-flonum->ordinal y) (single-flonum->ordinal x))) (define (ulp-difference x y) - (((flag 'precision 'double) flonums-between single-flonums-between) x y)) - -(define (*bit-width*) ((flag 'precision 'double) 64 32)) + (match* (x y) + [((? real?) (? real?)) + (if (flag-set? 'precision 'double) (flonums-between x y) (single-flonums-between x y))] + [((? complex?) (? complex?)) + (+ (ulp-difference (real-part x) (real-part y)) + (ulp-difference (imag-part x) (imag-part y)))] + [((? boolean?) (? boolean?)) + (if (equal? x y) 0 64)])) + +(define (midpoint-float p1 p2) + (cond + [(and (double-flonum? p1) (double-flonum? p2)) + (flstep p1 (quotient (flonums-between p1 p2) 2))] + [(and (single-flonum? p1) (single-flonum? p2)) + (floating-point-bytes->real + (integer->integer-bytes + (quotient + (+ (single-flonum->ordinal p1) (single-flonum->ordinal p2)) + 2) + 4) #f)] + [else + (error "Mixed precisions in binary search")])) + +(define (*bit-width*) (if (flag-set? 'precision 'double) 64 32)) (define (ulps->bits x) (cond @@ -29,3 +50,9 @@ (define (bit-difference x y) (ulps->bits (+ 1 (abs (ulp-difference x y))))) + +(define (sample-float) + (floating-point-bytes->real (integer->integer-bytes (random-exp 32) 4 #f))) + +(define (sample-double) + (floating-point-bytes->real (integer->integer-bytes (random-exp 64) 8 #f))) diff --git a/src/formats/c.rkt b/src/formats/c.rkt index 96ba3f4c6..b3e6b9c47 100644 --- a/src/formats/c.rkt +++ b/src/formats/c.rkt @@ -10,110 +10,29 @@ (define (fix-name name) (string-replace (uri-encode (~a name)) #rx"[^a-zA-Z0-9]" "_")) -(define (apply-converter conv args) - (cond - [(string? conv) (apply format conv args)] - [(list? conv) (apply format (list-ref conv (length args)) args)] - [(procedure? conv) (apply conv args)] - [else (error "Unknown syntax entry" conv)])) - -(define-table operators->c - [+ "~a + ~a"] - [- '(#f "-~a" "~a - ~a")] - [* "~a * ~a"] - [/ '(#f "1.0/~a" "~a / ~a")] - - [sqr (lambda (x) (format "~a * ~a" x x))] - [cube (lambda (x) (format "~a * (~a * ~a)" x x x))] - - [acos "acos(~a)"] - [acosh "acosh(~a)"] - [asin "asin(~a)"] - [asinh "asinh(~a)"] - [atan "atan(~a)"] - [atan2 "atan2(~a, ~a)"] - [atanh "atanh(~a)"] - [cbrt "cbrt(~a)"] - [ceil "ceil(~a)"] - [copysign "copysign(~a, ~a)"] - [cos "cos(~a)"] - [cosh "cosh(~a)"] - [erf "erf(~a)"] - [erfc "erfc(~a)"] - [exp "exp(~a)"] - [exp2 "exp2(~a)"] - [expm1 "expm1(~a)"] - [fabs "fabs(~a)"] - [fdim "fdim(~a, ~a)"] - [floor "floor(~a)"] - [fma "fma(~a, ~a, ~a)"] - [fmax "fmax(~a, ~a)"] - [fmin "fmin(~a, ~a)"] - [fmod "fmod(~a, ~a)"] - [hypot "hypot(~a, ~a)"] - [j0 "j0(~a)"] - [j1 "j1(~a)"] - [lgamma "lgamma(~a)"] - [log "log(~a)"] - [log10 "log10(~a)"] - [log1p "log1p(~a)"] - [log2 "log2(~a)"] - [logb "logb(~a)"] - [pow "pow(~a, ~a)"] - [remainder "remainder(~a, ~a)"] - [rint "rint(~a)"] - [round "round(~a)"] - [sin "sin(~a)"] - [sinh "sinh(~a)"] - [sqrt "sqrt(~a)"] - [tan "tan(~a)"] - [tanh "tanh(~a)"] - [tgamma "tgamma(~a)"] - [trunc "trunc(~a)"] - [y0 "y0(~a)"] - [y1 "y1(~a)"] - - [if "~a ? ~a : ~a"] - [== "~a == ~a"] - [!= "~a != ~a"] - [> "~a > ~a"] - [< "~a < ~a"] - [>= "~a >= ~a"] - [<= "~a <= ~a"] - ; TODO what should not be? - [and "~a && ~a"] - [or "~a || ~a"]) - - -(define-table constants->c - [PI "atan2(1.0, 0.0)"] - [E "exp(1.0)"]) - (define (comparison? l) (and (list? l) (member (car l) '(< > <= >= and or)))) -(define (if? l) - (and (list? l) (eq? (car l) 'if))) - (define (program->c prog [type "double"] [fname "f"]) (define vars (program-variables prog)) (define unused-vars (unused-variables prog)) (define body (compile (program-body prog))) - (define (value->c expr) + (define/contract (value->c expr) + (-> expr? string?) (cond [(member expr vars) (fix-name expr)] - [(member expr constants) (apply-converter (car (hash-ref constants->c expr)) '())] - [(symbol? expr) expr] + [(number? expr) (~a expr)] + [(constant? expr) (constant-info expr '->c/double)] + [(symbol? expr) (~a expr)] ; intermediate variable [else (define val (real->double-flonum (->flonum expr))) (if (equal? type "float") (format "~af" val) (~a val))])) - (define (app->c expr) + (define/contract (app->c expr) + (-> expr? string?) (if (list? expr) - (let* ([rec (list-ref (hash-ref operators->c (car expr)) 0)] - [args (map value->c (cdr expr))]) - (apply-converter rec args)) + (apply (operator-info (car expr) '->c/double) (map value->c (cdr expr))) (value->c expr))) (write-string @@ -136,114 +55,21 @@ (printf " return ~a;\n" (value->c (caddr body))) (printf "}\n\n"))) -(define-table operators->mpfr - [+ "mpfr_add(~a, ~a, ~a, MPFR_RNDN)"] - [- '(#f - #f - "mpfr_neg(~a, ~a, MPFR_RNDN)" - "mpfr_sub(~a, ~a, ~a, MPFR_RNDN)")] - [* "mpfr_mul(~a, ~a, ~a, MPFR_RNDN)"] - [/ '(#f - #f - "mpfr_ui_div(~a, 1, ~a, MPFR_RNDN)" - "mpfr_div(~a, ~a, ~a, MPFR_RNDN)")] - - [sqr "mpfr_sqr(~a, ~a, MPFR_RNDN)"] - [cube (λ (x y) (string-append - (format "mpfr_mul(~a, ~a, ~a, MPFR_RNDN); " - x y y) - (format "mpfr_mul(~a, ~a, ~a, MPFR_RNDN)" - x x y)))] - - [acos "mpfr_acos(~a, ~a, MPFR_RNDN)"] - [acosh "mpfr_acosh(~a, ~a, MPFR_RNDN)"] - [asin "mpfr_asin(~a, ~a, MPFR_RNDN)"] - [asinh "mpfr_asinh(~a, ~a, MPFR_RNDN)"] - [atan "mpfr_atan(~a, ~a, MPFR_RNDN)"] - [atan2 "mpfr_atan2(~a, ~a, ~a, MPFR_RNDN)"] - [atanh "mpfr_atanh(~a, ~a, MPFR_RNDN)"] - [cbrt "mpfr_cbrt(~a, ~a, MPFR_RNDN)"] - [ceil "mpfr_ceil(~a, ~a)"] - [copysign "mpfr_copysign(~a, ~a, ~a, MPFR_RNDN)"] - [cos "mpfr_cos(~a, ~a, MPFR_RNDN)"] - [cosh "mpfr_cosh(~a, ~a, MPFR_RNDN)"] - [erf "mpfr_erf(~a, ~a, MPFR_RNDN)"] - [erfc "mpfr_erfc(~a, ~a, MPFR_RNDN)"] - [exp "mpfr_exp(~a, ~a, MPFR_RNDN)"] - [exp2 "mpfr_exp2(~a, ~a, MPFR_RNDN)"] - [expm1 "mpfr_expm1(~a, ~a, MPFR_RNDN)"] - [fabs "mpfr_abs(~a, ~a, MPFR_RNDN)"] - [fdim "mpfr_dim(~a, ~a, ~a, MPFR_RNDN)"] - [floor "mpfr_floor(~a, ~a)"] - [fma "mpfr_fma(~a, ~a, ~a, ~a, MPFR_RNDN)"] - [fmax "mpfr_fmax(~a, ~a, ~a, MPFR_RNDN)"] - [fmin "mpfr_fmin(~a, ~a, ~a, MPFR_RNDN)"] - [fmod "mpfr_fmod(~a, ~a, ~a, MPFR_RNDN)"] - [hypot "mpfr_hypot(~a, ~a, ~a, MPFR_RNDN)"] - [j0 "mpfr_j0(~a, ~a, MPFR_RNDN)"] - [j1 "mpfr_j1(~a, ~a, MPFR_RNDN)"] - [lgamma "mpfr_lngamma(~a, ~a, MPFR_RNDN)"] - [log "mpfr_log(~a, ~a, MPFR_RNDN)"] - [log10 "mpfr_log10(~a, ~a, MPFR_RNDN)"] - [log1p "mpfr_log1p(~a, ~a, MPFR_RNDN)"] - [log2 "mpfr_log2(~a, ~a, MPFR_RNDN)"] - [logb "mpfr_set_si(~a, mpfr_get_exp(~a), MPFR_RNDN)"] - [pow "mpfr_pow(~a, ~a, ~a, MPFR_RNDN)"] - [remainder "mpfr_remainder(~a, ~a, ~a, MPFR_RNDN)"] - [rint "mpfr_rint(~a, ~a, MPFR_RNDN)"] - [round "mpfr_round(~a, ~a)"] - [sin "mpfr_sin(~a, ~a, MPFR_RNDN)"] - [sinh "mpfr_sinh(~a, ~a, MPFR_RNDN)"] - [sqrt "mpfr_sqrt(~a, ~a, MPFR_RNDN)"] - [tan "mpfr_tan(~a, ~a, MPFR_RNDN)"] - [tanh "mpfr_tanh(~a, ~a, MPFR_RNDN)"] - [tgamma "mpfr_gamma(~a, ~a, MPFR_RNDN)"] - [trunc "mpfr_trunc(~a, ~a)"] - [y0 "mpfr_y0(~a, ~a, MPFR_RNDN)"] - [y1 "mpfr_y1(~a, ~a, MPFR_RNDN)"] - - [if (lambda (r c a b) (string-append - (format "if (mpfr_get_si(~a, MPFR_RNDN)) { " c) - (format "mpfr_set(~a, ~a, MPFR_RNDN); " r a) - "} else { " - (format "mpfr_set(~a, ~a, MPFR_RNDN); " r b) - "}"))] - [> "mpfr_set_si(~a, mpfr_cmp(~a, ~a) > 0, MPFR_RNDN)"] - [< "mpfr_set_si(~a, mpfr_cmp(~a, ~a) < 0, MPFR_RNDN)"] - [>= "mpfr_set_si(~a, mpfr_cmp(~a, ~a) >= 0, MPFR_RNDN)"] - [<= "mpfr_set_si(~a, mpfr_cmp(~a, ~a) <= 0, MPFR_RNDN)"] - ; TODO what should not be? - [and (string-append - "mpfr_set_si(~a, " - "mpfr_get_si(~a, MPFR_RNDN) && " - "mpfr_get_si(~a, MPFR_RNDN), " - "MPFR_RNDN)")] - [or (string-append - "mpfr_set_si(~a, " - "mpfr_get_si(~a, MPFR_RNDN) || " - "mpfr_get_si(~a, MPFR_RNDN), " - "MPFR_RNDN)")]) - -(define-table constants->mpfr - [PI "mpfr_const_pi(~a, MPFR_RNDN)"] - [E (λ (x) (format "mpfr_set_si(~a, 1, MPFR_RNDN); mpfr_exp(~a, ~a, MPFR_RNDN);" x x x))]) - (define (program->mpfr prog [bits 128] [fname "f"]) (define vars (program-variables prog)) (define unused-vars (unused-variables prog)) (define body (compile (program-body prog))) - (define (app->mpfr out expr) + (define/contract (app->mpfr out expr) + (-> string? expr? string?) (cond [(list? expr) - (let* ([rec (list-ref (hash-ref operators->mpfr (car expr)) 0)] - [args (cdr expr)]) - (apply-converter rec (cons out args)))] + (apply (operator-info (car expr) '->c/mpfr) out (map ~a (cdr expr)))] [(number? expr) ""] [(member expr vars) (format "mpfr_set_d(~a, ~a, MPFR_RNDN)" out (fix-name expr))] - [(member expr constants) - (apply-converter (car (hash-ref constants->mpfr expr)) (list out))] + [(constant? expr) + ((constant-info expr '->c/mpfr) out)] [(symbol? expr) (format "mpfr_set(~a, ~a, MPFR_RNDN)" out expr)])) @@ -269,7 +95,7 @@ (string-join pdecls ", "))) (for ([assignment (cadr body)]) - (printf " ~a;\n" (app->mpfr (car assignment) (cadr assignment)))) + (printf " ~a;\n" (app->mpfr (~a (car assignment)) (cadr assignment)))) (printf " return mpfr_get_d(~a, MPFR_RNDN);\n" (caddr body)) @@ -298,11 +124,11 @@ (define (compile-info base-dir single-info double-info) (for ([single-test (report-info-tests single-info)] [double-test (report-info-tests double-info)]) - (when (and (not (member (table-row-status single-test) '("timeout" "crash"))) - (not (member (table-row-status double-test) '("timeout" "crash")))) + (when (and (not (member (table-row-status single-test) '("timeout" "error" "crash"))) + (not (member (table-row-status double-test) '("timeout" "error" "crash")))) (match (cons single-test double-test) - [(cons (table-row name single-status _ _ _ _ _ _ vars _ input single-output _ single-bits dir) - (table-row name double-status _ _ _ _ _ _ vars _ input double-output _ double-bits dir)) + [(cons (table-row name single-status _ _ _ _ _ _ _ vars input single-output _ single-bits dir) + (table-row name double-status _ _ _ _ _ _ _ vars input double-output _ double-bits dir)) (define fname (build-path base-dir dir "compiled.c")) (debug #:from 'compile-info "Compiling" name "to" fname) (write-file fname diff --git a/src/formats/datafile.rkt b/src/formats/datafile.rkt index f3df78d8c..0df502a24 100644 --- a/src/formats/datafile.rkt +++ b/src/formats/datafile.rkt @@ -11,15 +11,16 @@ (struct table-row - (name status start result target inf- inf+ result-est vars samplers input output time bits link) #:prefab) + (name status start result target inf- inf+ start-est result-est vars input output time bits link) #:prefab) (struct report-info - (date commit branch seed flags points iterations bit-width note tests) #:prefab #:mutable) + (date commit branch hostname seed flags points iterations bit-width note tests) #:prefab #:mutable) (define (make-report-info tests #:note [note ""] #:seed [seed #f]) (report-info (current-date) *herbie-commit* *herbie-branch* + *hostname* (or seed (get-seed)) (*flags*) (*num-points*) @@ -32,7 +33,7 @@ (define (simplify-test test) (match test [(table-row name status start-bits end-bits target-bits - inf- inf+ end-est vars samplers input output time bits link) + inf- inf+ start-est end-est vars input output time bits link) (make-hash `((name . ,name) (status . ,status) @@ -43,7 +44,6 @@ (pinf . ,inf+) (end-est . ,end-est) (vars . ,(if vars (map symbol->string vars) #f)) - (samplers . ,(if samplers (map ~a samplers) #f)) (input . ,(~a input)) (output . ,(~a output)) (time . ,time) @@ -52,11 +52,12 @@ (define data (match info - [(report-info date commit branch seed flags points iterations bit-width note tests) + [(report-info date commit branch hostname seed flags points iterations bit-width note tests) (make-hash `((date . ,(date->seconds date)) (commit . ,commit) (branch . ,branch) + (hostname . ,hostname) (seed . ,(~a seed)) (flags . ,(flags->list flags)) (points . ,points) @@ -84,7 +85,8 @@ (let* ([json (call-with-input-file file read-json)] [get (λ (field) (hash-ref json field))]) - (report-info (seconds->date (get 'date)) (get 'commit) (get 'branch) (parse-string (get 'seed)) + (report-info (seconds->date (get 'date)) (get 'commit) (get 'branch) (hash-ref json 'hostname "") + (parse-string (get 'seed)) (list->flags (get 'flags)) (get 'points) (get 'iterations) (hash-ref json 'bit_width 64) (hash-ref json 'note #f) (for/list ([test (get 'tests)] #:when (hash-has-key? test 'vars)) @@ -94,12 +96,7 @@ (match (hash-ref test 'vars) [(list names ...) (map string->symbol names)] [string-lst (parse-string string-lst)])) - (define samplers - (match (hash-ref test 'samplers #f) - ['#f (if vars (map (const 'default) vars) #f)] - [(list sampler ...) (map parse-string sampler)])) (table-row (get 'name) (get 'status) (get 'start) (get 'end) (get 'target) - (get 'ninf) (get 'pinf) (hash-ref test 'end-est 0) - vars samplers - (parse-string (get 'input)) (parse-string (get 'output)) + (get 'ninf) (get 'pinf) (hash-ref test 'start-est 0) (hash-ref test 'end-est 0) + vars (parse-string (get 'input)) (parse-string (get 'output)) (get 'time) (get 'bits) (get 'link))))))) diff --git a/src/formats/test.rkt b/src/formats/test.rkt index 307574126..1f9a2a53a 100644 --- a/src/formats/test.rkt +++ b/src/formats/test.rkt @@ -4,11 +4,11 @@ (require "../errors.rkt") (require "../alternative.rkt") (require "../programs.rkt") -(require "../range-analysis.rkt") -(require "../syntax/distributions.rkt") +(require "../syntax-check.rkt") +(require "../type-check.rkt") -(provide (struct-out test) test-program test-samplers - load-tests load-file test-target parse-test unparse-test test-successful? test= input-bits output-bits)] [(_ #t) (>= target-bits (- output-bits 1))])) -(struct test (name vars sampling-expr input output expected precondition) #:prefab) - -(define (test-samplers test) - (define range-table (condition->range-table (test-precondition test))) - (for/list ([var (test-vars test)] [samp (test-sampling-expr test)]) - (define intvl (range-table-ref range-table var)) - (unless intvl - (raise-herbie-error "No valid values of variable ~a" var - #:url "faq.html#no-valid-values")) - (cons var (eval-sampler samp intvl)))) +(struct test (name vars input output expected precondition) #:prefab) (define (parse-test stx) (assert-program! stx) + (assert-program-type! stx) (define expr (syntax->datum stx)) (match expr [(list 'FPCore (list args ...) props ... body) (define prop-dict (let loop ([props props] [out '()]) (if (null? props) - out + (reverse out) (loop (cddr props) (cons (cons (first props) (second props)) out))))) - (define samp-dict (dict-ref prop-dict ':herbie-samplers '())) - (define samps (map (lambda (x) (car (dict-ref samp-dict x '(default)))) args)) - - (define body* (desugar-program body)) (test (~a (dict-ref prop-dict ':name body)) - args samps - body* - (desugar-program (dict-ref prop-dict ':target #f)) + args + (desugar-program body) + (desugar-program (dict-ref prop-dict ':herbie-target #f)) (dict-ref prop-dict ':herbie-expected #t) - (dict-ref prop-dict ':pre 'TRUE))] + (desugar-program (dict-ref prop-dict ':pre 'TRUE)))] [(list (or 'λ 'lambda 'define 'herbie-test) _ ...) (raise-herbie-error "Herbie 1.0+ no longer supports input formats other than FPCore." #:url "input.html")] [_ (raise-herbie-error "Invalid input expression." #:url "input.html")])) -(define (unparse-test expr) - (match-define (list (or 'λ 'lambda) (list vars ...) body) expr) - `(FPCore (,@vars) ,body)) - (define (load-file file) (call-with-input-file file (λ (port) diff --git a/src/formats/tex.rkt b/src/formats/tex.rkt index 9289c341f..6f97d8312 100644 --- a/src/formats/tex.rkt +++ b/src/formats/tex.rkt @@ -8,36 +8,20 @@ (define mathjax-url "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML") -(define-table texify-constants - [l "\\ell"] - [PI "\\pi"] - [E "e"] - [eps "\\varepsilon"] - [epsilon "\\varepsilon"] - [alpha "\\alpha"] - [beta "\\beta"] - [gamma "\\gamma"] - [phi "\\phi"] - [phi1 "\\phi_1"] - [phi2 "\\phi_2"] - [lambda "\\lambda"] - [lambda1 "\\lambda_1"] - [lambda2 "\\lambda_2"]) - -; Note that normal string converters ignore idx and -; procedure converters take idx as first arg. -(define (apply-converter conv args [idx #f]) - (cond - [(string? conv) (apply format conv args)] - [(list? conv) (apply-converter (list-ref conv (length args)) args idx)] - [(procedure? conv) (apply conv (if idx (cons idx args) args))] - [else (error "Unknown syntax entry" conv)])) - -(define parens-precedence '(#t + * fn #f)) - -(define (parens-< a b) - (< (index-of parens-precedence a) - (index-of parens-precedence b))) +(define/match (texify-variable var) + [('l) "\\ell"] + [('eps) "\\varepsilon"] + [('epsilon) "\\varepsilon"] + [('alpha) "\\alpha"] + [('beta) "\\beta"] + [('gamma) "\\gamma"] + [('phi) "\\phi"] + [('phi1) "\\phi_1"] + [('phi2) "\\phi_2"] + [('lambda) "\\lambda"] + [('lambda1) "\\lambda_1"] + [('lambda2) "\\lambda_2"] + [(_) (symbol->string var)]) ; "enclose" is a MathJax extension which may ; not work with standard TeX processors. @@ -50,210 +34,53 @@ (define (untag str) (format "\\color{black}{~a}" str)) -(define ((tag-inner-untag str) idx . args) - (tag (apply format str (map untag args)) idx)) - -(define ((tag-infix op) idx arg1 arg2) - (format "~a ~a ~a" arg1 (tag op idx) arg2)) - ; self-paren-level : #t --> paren me ; #f --> do not paren me ; ; args-paren-level : #t --> do not paren args ; #f --> paren args -(define-table texify-operators - [+ '(#f "+~a" "~a + ~a") - `(#f ,(tag-inner-untag "+~a") - ,(tag-infix "+")) - '+ '+] - [- '(#f "-~a" "~a - ~a") - `(#f ,(tag-inner-untag "-~a") - ,(tag-infix "-")) - '+ '+] - [* "~a \\cdot ~a" - (tag-infix "\\cdot") - '* '*] - [/ '(#f "\\frac1{~a}" "\\frac{~a}{~a}") - `(#f ,(tag-inner-untag "\\frac1{~a}") - ,(tag-inner-untag "\\frac{~a}{~a}")) - #f #t] - - [sqr "{~a}^2" - (lambda (idx a) (format "{~a}^{~a}" a (tag "2" idx))) - #f #f] - [cube "{~a}^3" - (lambda (idx a) (format "{~a}^{~a}" a (tag "3" idx))) - #f #f] - [acos "\\cos^{-1} ~a" - (tag-inner-untag "\\cos^{-1} ~a") - 'fn #f] - [acosh "\\cosh^{-1} ~a" - (tag-inner-untag "\\cosh^{-1} ~a") - 'fn #f] - [asin "\\sin^{-1} ~a" - (tag-inner-untag "\\sin^{-1} ~a") - 'fn #f] - [asinh "\\sinh^{-1} ~a" - (tag-inner-untag "\\sinh^{-1} ~a") - 'fn #f] - [atan "\\tan^{-1} ~a" - (tag-inner-untag "\\tan^{-1} ~a") - 'fn #f] - [atan2 "\\tan^{-1}_* \\frac{~a}{~a}" - (tag-inner-untag "\\tan^{-1}_* \\frac{~a}{~a}") - 'fn #t] - [atanh "\\tanh^{-1} ~a" - (tag-inner-untag "\\tanh^{-1} ~a") - 'fn #f] - [cbrt "\\sqrt[3]{~a}" - (tag-inner-untag "\\sqrt[3]{~a}") - #f #t] - [ceil "\\left\\lceil~a\\right\\rceil" - (tag-inner-untag "\\left\\lceil~a\\right\\rceil") - #f #t] - [copysign "\\mathsf{copysign}\\left(~a, ~a\\right)" - (tag-inner-untag "\\mathsf{copysign}\\left(~a, ~a\\right)") - #f #t] - [cos "\\cos ~a" - (tag-inner-untag "\\cos ~a") - 'fn #f] - [cosh "\\cosh ~a" - (tag-inner-untag "\\cosh ~a") - 'fn #f] - [erf "\\mathsf{erf} ~a" - (tag-inner-untag "\\mathsf{erf} ~a") - 'fn #f] - [erfc "\\mathsf{erfc} ~a" - (tag-inner-untag "\\mathsf{erfc} ~a") - 'fn #f] - [exp "e^{~a}" - (tag-inner-untag "e^{~a}") - #f #t] - [exp2 "2^{~a}" - (tag-inner-untag "2^{~a}") - #f #t] - [expm1 "(e^{~a} - 1)^*" - (tag-inner-untag "(e^{~a} - 1)^*") - #f #t] - [fabs "\\left|~a\\right|" - (tag-inner-untag "\\left|~a\\right|") - #f #t] - [fdim "\\mathsf{fdim}\\left(~a, ~a\\right)" - (tag-inner-untag "\\mathsf{fdim}\\left(~a, ~a\\right)") - #f #t] - [floor "\\left\\lfloor~a\\right\\rfloor" - (tag-inner-untag "\\left\\lfloor~a\\right\\rfloor") - #f #t] - [fma "(~a \\cdot ~a + ~a)_*" - (tag-inner-untag "(~a \\cdot ~a + ~a)_*") - #f #f] - [fmax "\\mathsf{fmax}\\left(~a, ~a\\right)" - (tag-inner-untag "\\mathsf{fmax}\\left(~a, ~a\\right)") - #f #t] - [fmin "\\mathsf{fmin}\\left(~a, ~a\\right)" - (tag-inner-untag "\\mathsf{fmin}\\left(~a, ~a\\right)") - #f #t] - [fmod "~a \\bmod ~a" - (tag-infix "\\bmod") - #t #f] - [hypot "\\sqrt{~a^2 + ~a^2}^*" - (tag-inner-untag "\\sqrt{~a^2 + ~a^2}^*") - #f #f] - [j0 "\\mathsf{j0} ~a" - (tag-inner-untag "\\mathsf{j0} ~a") - 'fn #f] - [j1 "\\mathsf{j1} ~a" - (tag-inner-untag "\\mathsf{j1} ~a") - 'fn #f] - [lgamma "\\log_* \\left( \\mathsf{gamma} ~a \\right)" - (tag-inner-untag "\\log_* \\left( \\mathsf{gamma} ~a \\right)") - 'fn #f] - [log "\\log ~a" - (tag-inner-untag "\\log ~a") - 'fn #f] - [log10 "\\log_{10} ~a" - (tag-inner-untag "\\log_{10} ~a") - 'fn #f] - [log1p "\\log_* (1 + ~a)" - (tag-inner-untag "\\log_* (1 + ~a)") - #f '+] - [log2 "\\log_{2} ~a" - (tag-inner-untag "\\log_{2} ~a") - 'fn #f] - [logb "\\log^{*}_{b} ~a" - (tag-inner-untag "\\log^{*}_{b} ~a") - 'fn #f] - [pow "{~a}^{~a}" - (tag-inner-untag "{~a}^{~a}") - #f #f] - [remainder "~a \\mathsf{rem} ~a" - (tag-infix "\\mathsf{rem}") - #t #f] - [rint "\\mathsf{rint} ~a" - (tag-inner-untag "\\mathsf{rint} ~a") - 'fn #f] - [round "\\mathsf{round} ~a" - (tag-inner-untag "\\mathsf{round} ~a") - 'fn #f] - [sin "\\sin ~a" - (tag-inner-untag "\\sin ~a") - 'fn #f] - [sinh "\\sinh ~a" - (tag-inner-untag "\\sinh ~a") - 'fn #f] - [sqrt "\\sqrt{~a}" - (tag-inner-untag "\\sqrt{~a}") - #f #t] - [tan "\\tan ~a" - (tag-inner-untag "\\tan ~a") - 'fn #f] - [tanh "\\tanh ~a" - (tag-inner-untag "\\tanh ~a") - 'fn #f] - [tgamma "\\mathsf{gamma} ~a" - (tag-inner-untag "\\mathsf{gamma} ~a") - 'fn #f] - [trunc "\\mathsf{trunc} ~a" - (tag-inner-untag "\\mathsf{trunc} ~a") - 'fn #f] - [y0 "\\mathsf{y0} ~a" - (tag-inner-untag "\\mathsf{y0} ~a") - 'fn #f] - [y1 "\\mathsf{y1} ~a" - (tag-inner-untag "\\mathsf{y1} ~a") - 'fn #f] - - [if "~a ? ~a : ~a" - (lambda (idx a b c) - (format "~a ~a ~a : ~a" a (tag "?" idx) b c)) - #t #t] - [!= "~a \\ne ~a" - (tag-infix "\\ne") - #f #t] - [== "~a = ~a" - (tag-infix "=") - #f #t] - [> "~a \\gt ~a" - (tag-infix "\\gt") - #f #t] - [< "~a \\lt ~a" - (tag-infix "\\lt") - #f #t] - [>= "~a \\ge ~a" - (tag-infix "\\ge") - #f #t] - [<= "~a \\le ~a" - (tag-infix "\\le") - #f #t] - [not "\\neg ~a" - (tag-inner-untag "\\neg ~a") - 'fn #f] - [and "~a \\land ~a" - (tag-infix "\\land") - '* '*] - [or "~a \\lor ~a" - (tag-infix "\\lor") - '+ '+]) +(define precedence-ordering '(#t + * fn #f)) + +(define (precedence< a b) + (< (index-of precedence-ordering a) + (index-of precedence-ordering b))) + +(define (precedence-levels op) + (match op + [(or '+ '- 'or 'complex) (values '+ '+)] + [(or '* 'and) (values '* '*)] + ['/ (values #f #t)] + [(or 'sqr 'cube 'fma 'hypot 'pow) (values #f #f)] + ['atan2 (values 'fn #t)] + ['log1p (values #f '+)] + ['if (values #t #t)] + [(or 'remainder 'fmod) (values #t #f)] + [(or 'cbrt 'ceil 'copysign 'expm1 'exp2 'floor 'fmax 'exp 'sqrt 'fmin 'fabs 'fdim) + (values #f #t)] + [(or '== '< '> '<= '>= '!=) + (values #f #t)] + [_ (values 'fn #f)])) + +(define ((highlight-template op) idx args) + (define to-tag-infix + #hash((+ . "+") (- . "-") (* . "\\cdot") (fmod . "\\bmod") (remainder . "\\mathsf{rem}") + (< . "\\lt") (> . "\\gt") (== . "=") (!= . "\\ne") (<= . "\\le") (>= . "\\ge") + (and . "\\land") (or . "\\lor"))) + (cond + [(and (equal? (length args) 2) (hash-has-key? to-tag-infix op)) + (match-define (list a b) args) + (format "~a ~a ~a" a (tag (hash-ref to-tag-infix op) idx) b)] + [(equal? op 'if) + (match-define (list a b c) args) + (format "~a ~a ~a : ~a" a (tag "?" idx) b c)] + [(equal? op 'sqr) + (match-define (list a) args) + (format "{~a}^{~a}" a (tag "2" idx))] + [(equal? op 'cube) + (match-define (list a) args) + (format "{~a}^{~a}" a (tag "3" idx))] + [else + (tag (apply (operator-info op '->tex) (map untag args)) idx)])) (define (collect-branches expr loc) (match expr @@ -284,17 +111,23 @@ (format "\\frac{~a}{~a}" (numerator expr) (denominator expr))] [(? real?) (match (string-split (number->string expr) "e") + [(list "-inf.0") "-\\infty"] + [(list "+inf.0") "+\\infty"] [(list num) num] [(list significand exp) (define num (if (equal? significand "1") (format "10^{~a}" exp) (format "~a \\cdot 10^{~a}" significand exp))) - (if (parens-< parens #f) num (format "\\left( ~a \\right)" num))])] - [(? symbol?) - (if (hash-has-key? texify-constants expr) - (car (hash-ref texify-constants expr)) - (symbol->string expr))] + (if (precedence< parens #f) num (format "\\left( ~a \\right)" num))])] + [(? complex?) + (format "~a ~a ~a i" + (texify (real-part expr) '+ loc) + (if (or (< (imag-part expr) 0) (equal? (imag-part expr) -0.0)) '- '+) + (texify (abs (imag-part expr)) '+ loc))] + [(? constant?) + (constant-info expr '->tex)] + [(? symbol?) (texify-variable expr)] [`(if ,cond ,ift ,iff) (define NL "\\\\\n") (define IND "\\;\\;\\;\\;") @@ -311,22 +144,23 @@ (texify bcond #t (cons 1 bloc)) NL IND (texify bexpr #t (cons 2 bloc)) NL)])) (printf "\\end{array}")))] + [`(<= ,x -inf.0) + (texify `(== ,x -inf.0) parens loc)] [`(,f ,args ...) - (match (hash-ref texify-operators f) - [(list template highlight-template self-paren-level arg-paren-level) - (let ([texed-args - (for/list ([arg args] [id (in-naturals 1)]) - (texify arg arg-paren-level (cons id loc)))] - [hl-loc - (assoc (reverse loc) highlight-locs)]) - (format - ; omit parens if parent contex has lower precedence - (if (parens-< parens self-paren-level) - "~a" - "\\left(~a\\right)") - (if hl-loc - (apply-converter highlight-template texed-args (cdr hl-loc)) - (apply-converter template texed-args))))])])))) + (define-values (self-paren-level arg-paren-level) (precedence-levels f)) + (let ([texed-args + (for/list ([arg args] [id (in-naturals 1)]) + (texify arg arg-paren-level (cons id loc)))] + [hl-loc + (assoc (reverse loc) highlight-locs)]) + (format + ; omit parens if parent contex has lower precedence + (if (precedence< parens self-paren-level) + "~a" + "\\left(~a\\right)") + (if hl-loc + ((highlight-template f) (cdr hl-loc) texed-args) + (apply (operator-info f '->tex) texed-args))))])))) ; TODO probably a better way to write this wrapper using ; make-keyword-procedure and keyword-apply @@ -341,3 +175,4 @@ (define (exact-rational? r) (and (rational? r) (exact? r))) + diff --git a/src/glue.rkt b/src/glue.rkt index 59517b9d6..0ffe0efb4 100644 --- a/src/glue.rkt +++ b/src/glue.rkt @@ -2,7 +2,6 @@ (require "common.rkt") (require "points.rkt") -(require "syntax/distributions.rkt") (require "alternative.rkt") (require "programs.rkt") (require "core/simplify.rkt") @@ -12,8 +11,9 @@ (require "core/taylor.rkt") (require "core/alt-table.rkt") (require "core/matcher.rkt") +(require "type-check.rkt") -(provide remove-pows setup-prog post-process +(provide remove-pows setup-prog setup-alt-simplified post-process split-table extract-alt combine-alts best-alt simplify-alt completely-simplify-alt taylor-alt zach-alt) @@ -23,58 +23,50 @@ ;; Implementation (define (remove-pows altn) - (alt-event - `(λ ,(program-variables (alt-program altn)) - ,(let loop ([cur-expr (program-body (alt-program altn))]) - (cond [(and (list? cur-expr) (eq? 'expt (car cur-expr)) - (let ([exponent (caddr cur-expr)]) - (and (not (list? exponent)) - (not (symbol? exponent)) - (positive? exponent) - (integer? exponent) - (exponent . < . 10)))) - (let inner-loop ([pows-left (caddr cur-expr)]) - (if (pows-left . = . 1) - (cadr cur-expr) - (list '* (cadr cur-expr) (inner-loop (sub1 pows-left)))))] - [(list? cur-expr) - (cons (car cur-expr) (map loop (cdr cur-expr)))] - [#t cur-expr]))) - 'removed-pows - (list altn))) + (define body* + (let loop ([expr (program-body (alt-program altn))]) + (match expr + [(list 'expt base (and (? integer?) (? positive?) (? (curryr < 10)) exponent)) + (for/fold ([term base]) ([i (in-range 1 exponent)]) + (list '* base term))] + [(list op args ...) + (cons op (map loop args))] + [_ expr]))) + (if (equal? body* (program-body (alt-program altn))) + altn + (alt-event `(λ ,(program-variables (alt-program altn)) ,body*) + 'removed-pows (list altn)))) (define (setup-prog prog fuel) (let* ([alt (make-alt prog)] - [maybe-simplify ((flag 'setup 'simplify) simplify-alt identity)] - [processed (maybe-simplify alt)] - [table (make-alt-table (*pcontext*) processed)] + [table (make-alt-table (*pcontext*) alt)] [extracted (atab-all-alts table)]) - (assert (equal? extracted (list processed)) + (assert (equal? extracted (list alt)) #:extra-info (λ () (format "Extracted is ~a, but we gave it ~a" - extracted processed))) + extracted alt))) table)) +(define (setup-alt-simplified prog) + (let* ([alt (make-alt prog)] + [maybe-simplify (if (flag-set? 'setup 'simplify) simplify-alt identity)] + [processed (maybe-simplify alt)]) + processed)) + (define (extract-alt table) (parameterize ([*pcontext* (atab-context table)]) (argmin alt-history-length (argmins alt-cost (argmins (compose errors-score alt-errors) - (map simplify-alt (atab-all-alts table))))))) + (atab-all-alts table)))))) (define (combine-alts splitpoints alts) - (let ([rsplits (reverse splitpoints)]) - (make-regime-alt - `(λ ,(program-variables (*start-prog*)) - ,(let loop ([rest-splits (cdr rsplits)] - [acc (program-body (alt-program (list-ref alts (sp-cidx (car rsplits)))))]) - (if (null? rest-splits) acc - (loop (cdr rest-splits) - (let ([splitpoint (car rest-splits)]) - `(if (<= ,(sp-bexpr splitpoint) - ,(sp-point splitpoint)) - ,(program-body (alt-program (list-ref alts (sp-cidx splitpoint)))) - ,acc)))))) - alts splitpoints))) + (define expr + (for/fold + ([expr (program-body (alt-program (list-ref alts (sp-cidx (last splitpoints)))))]) + ([splitpoint (cdr (reverse splitpoints))]) + (define test `(<= ,(sp-bexpr splitpoint) ,(sp-point splitpoint))) + `(if ,test ,(program-body (alt-program (list-ref alts (sp-cidx splitpoint)))) ,expr))) + (make-regime-alt `(λ ,(program-variables (*start-prog*)) ,expr) alts splitpoints)) (define (best-alt alts) (argmin alt-cost @@ -97,7 +89,7 @@ (let* ([all-alts (atab-all-alts table)] [num-alts (length all-alts)] [zached-alts 0] - [maybe-zach ((flag 'reduce 'zach) + [maybe-zach (if (flag-set? 'reduce 'zach) (λ (alt locs) (debug #:from 'progress #:depth 3 "zaching alt" (add1 zached-alts) "of" num-alts) (log! 'zach) @@ -105,7 +97,7 @@ (append-map (curry zach-alt alt) locs)) (const '()))] [taylored-alts 0] - [maybe-taylor ((flag 'reduce 'taylor) + [maybe-taylor (if (flag-set? 'reduce 'taylor) (λ (alt locs) (debug #:from 'progress #:depth 3 "tayloring alt" (add1 taylored-alts) "of" num-alts) (log! 'series) @@ -120,7 +112,7 @@ (append (maybe-zach alt locs) (maybe-taylor alt locs))))] [num-alts* (length alts*)] [simplified-alts 0] - [maybe-simplify ((flag 'reduce 'simplify) + [maybe-simplify (if (flag-set? 'reduce 'simplify) (λ (alt) (debug #:from 'progress #:depth 3 "simplifying alt" (add1 simplified-alts) "of" num-alts*) (log! 'simplify) @@ -141,27 +133,26 @@ (define (taylor-alt altn loc) ; BEWARE WHEN EDITING: the free variables of an expression can be null - (for/list ([transform transforms-to-try]) - (match transform - [(list name f finv) - (alt-add-event - (make-delta altn - (location-do loc (alt-program altn) - (λ (expr) (let ([fv (free-variables expr)]) - (if (null? fv) expr - (approximate expr fv #:transform (map (const (cons f finv)) fv)))))) - 'taylor) - `(taylor ,name ,loc))]))) - -(define (make-delta old-alt new-prog name) - (alt-delta new-prog (change (rule name (alt-program old-alt) new-prog) '() - (for/list ([var (program-variables new-prog)]) (cons var var))) - old-alt)) + (define expr (location-get loc (alt-program altn))) + (match (type-of expr (for/hash ([var (free-variables expr)]) (values var 'real))) + ['real + (for/list ([transform transforms-to-try]) + (match transform + [(list name f finv) + (alt-event + (location-do loc (alt-program altn) + (λ (expr) (let ([fv (free-variables expr)]) + (if (null? fv) expr + (approximate expr fv #:transform (map (const (cons f finv)) fv)))))) + `(taylor ,name ,loc) + (list altn))]))] + ['complex + (list altn)])) (define (zach-alt altn loc) (let ([sibling (location-sibling loc)] [rewrite - ((flag 'generate 'rm) alt-rewrite-rm alt-rewrite-expression)]) + (if (flag-set? 'generate 'rm) alt-rewrite-rm alt-rewrite-expression)]) (if (and sibling (= (length (location-get (location-parent loc) (alt-program altn))) 3)) diff --git a/src/herbie.rkt b/src/herbie.rkt index e65e21ab7..332d26711 100644 --- a/src/herbie.rkt +++ b/src/herbie.rkt @@ -1,18 +1,30 @@ #lang racket (require racket/lazy-require) -(require "common.rkt" "multi-command-line.rkt" "sandbox.rkt" "errors.rkt") +(require "common.rkt" "multi-command-line.rkt" "sandbox.rkt" "errors.rkt" + "syntax/syntax.rkt" "syntax/rules.rkt") (lazy-require ["web/demo.rkt" (run-demo)] ["reports/run.rkt" (make-report)] ["shell.rkt" (run-shell)] - ["improve.rkt" (run-improve)] - ["old/herbie.rkt" (run-herbie)]) + ["improve.rkt" (run-improve)]) (define (string->thread-count th) (match th ["no" #f] ["yes" (max (- (processor-count) 1) 1)] [_ (string->number th)])) +(define (check-operator-fallbacks!) + (prune-operators!) + (prune-rules!) + (unless (null? (if (flag-set? 'precision 'double) (*unknown-d-ops*) (*unknown-f-ops*))) + (eprintf "Warning: native ~a not supported on your system; " + (string-join (map ~a (if (flag-set? 'precision 'double) (*unknown-d-ops*) (*unknown-f-ops*))) + ", ")) + (eprintf (if (flag-set? 'precision 'fallback) "fallbacks will be used.\n" "functions are disabled.\n")) + (eprintf "See for more info.\n" + *herbie-version*)) + (unless (flag-set? 'fn 'cbrt) (eprintf "cbrt is diabled.\n"))) + (module+ main (define quiet? #f) (define demo-output #f) @@ -25,13 +37,15 @@ (define report-profile? #f) (define report-note #f) + (define seed (random 1 (expt 2 31))) + (set-seed! seed) (multi-command-line #:program "herbie" #:once-each [("--timeout") s "Timeout for each test (in seconds)" (*timeout* (* 1000 (string->number s)))] - [("--seed") rs "The random seed vector to use in point generation. If false (#f), a random seed is used" + [("--seed") rs "The random seed to use in point generation" (define given-seed (read (open-input-string rs))) (when given-seed (set-seed! given-seed))] [("--num-iters") fu "The number of iterations of the main loop to use" @@ -53,6 +67,7 @@ #:subcommands [shell "Interact with Herbie from the shell" #:args () + (check-operator-fallbacks!) (run-shell)] [web "Interact with Herbie from your browser" #:once-each @@ -69,12 +84,14 @@ [("--quiet") "Print a smaller banner and don't start a browser." (set! quiet? true)] #:args () + (check-operator-fallbacks!) (run-demo #:quiet quiet? #:output demo-output #:log demo-log #:prefix demo-prefix #:demo? demo? #:port demo-port)] [improve "Run Herbie on an FPCore file, producing an FPCore file" #:once-each [("--threads") th "How many tests to run in parallel: 'yes', 'no', or a number" (set! threads (string->thread-count th))] #:args (input output) + (check-operator-fallbacks!) (run-improve input output #:threads threads)] [report "Run Herbie on an FPCore file, producing an HTML report" #:once-each @@ -85,13 +102,15 @@ [("--profile") "Whether to profile each run" (set! report-profile? true)] #:args (input output) + (check-operator-fallbacks!) (make-report (list input) #:dir output #:profile report-profile? #:note report-note #:threads threads)] #:args files (begin - (eprintf "Deprecated command-line syntax used.\n") - (if (null? files) - (eprintf " cmdline::: Use `herbie shell` to use Herbie on the command line\n") - (eprintf " cmdline::: Use `herbie improve` to run Herbie on FPCore files\n")) - (eprintf "See for more.\n") - (run-herbie files)))) + (match files + ['() + (eprintf "Please specify a Herbie tool, such as `herbie shell`.\n") + (eprintf "See for more.\n" *herbie-version*)] + [(cons tool _) + (eprintf "Unknown Herbie tool `~a`. See a list of available tools with `herbie --help`.\n" tool) + (eprintf "See for more.\n" *herbie-version*)])))) diff --git a/src/improve.rkt b/src/improve.rkt index 0ba444fd6..5e9efa131 100644 --- a/src/improve.rkt +++ b/src/improve.rkt @@ -5,23 +5,30 @@ (define (print-outputs tests results p #:seed [seed #f]) (when seed (fprintf p ";; seed: ~a\n\n" seed)) - (for ([res results] [test tests]) - (match-define (table-row name status start result target inf- inf+ result-elt vars samplers input output time bits link) res) + (for ([res results] [test tests] #:when res) + (match-define (table-row name status start result target inf- inf+ start-est result-est vars input output time bits link) (cdr res)) (match status - [(? test-failure?) + ["error" + (fprintf p ";; Error in ~a\n" name) + (write (car res) p) + (newline p)] + ["crash" (fprintf p ";; Crash in ~a\n" name) - (fprintf p "~a\n" `(FPCore ,vars ,input))] - [(? test-timeout?) + (write (car res) p) + (newline p)] + ["timeout" (fprintf p ";; ~a times out in ~as\n" (/ (*timeout*) 1000) name) - (fprintf p "~a\n" `(FPCore ,vars ,input))] - [_ - (fprintf p "~a\n" `(FPCore ,vars ,output))]))) + (write (car res) p) + (newline p)] + [(? string?) + (write (car res) p) + (newline p)]))) (define (run-improve input output #:threads [threads #f]) (define seed (get-seed)) (define tests (load-tests input)) - (define results (get-test-results tests #:threads threads #:seed seed #:dir #f)) + (define results (get-test-results tests #:threads threads #:seed seed #:profile #f #:dir #f)) (if (equal? output "-") (print-outputs tests results (current-output-port) #:seed seed) diff --git a/src/mainloop.rkt b/src/mainloop.rkt index 6c1a204ee..364dc068a 100644 --- a/src/mainloop.rkt +++ b/src/mainloop.rkt @@ -4,7 +4,6 @@ (require "glue.rkt") (require "programs.rkt") (require "points.rkt") -(require "syntax/distributions.rkt") (require "core/localize.rkt") (require "core/taylor.rkt") (require "core/alt-table.rkt") @@ -27,10 +26,10 @@ ;; head at once, because then global state is going to mess you up. (struct shellstate - (table next-alt locs children gened-series gened-rewrites simplified samplers precondition timeline) + (table next-alt locs children gened-series gened-rewrites simplified precondition timeline) #:mutable) -(define ^shell-state^ (make-parameter (shellstate #f #f #f '() #f #f #f #f 'TRUE '()))) +(define ^shell-state^ (make-parameter (shellstate #f #f #f #f #f #f #f 'TRUE '()))) (define (^locs^ [newval 'none]) (when (not (equal? newval 'none)) (set-shellstate-locs! (^shell-state^) newval)) @@ -44,9 +43,6 @@ (define (^children^ [newval 'none]) (when (not (equal? newval 'none)) (set-shellstate-children! (^shell-state^) newval)) (shellstate-children (^shell-state^))) -(define (^samplers^ [newval 'none]) - (when (not (equal? newval 'none)) (set-shellstate-samplers! (^shell-state^) newval)) - (shellstate-samplers (^shell-state^))) (define (^precondition^ [newval 'none]) (when (not (equal? newval 'none)) (set-shellstate-precondition! (^shell-state^) newval)) (shellstate-precondition (^shell-state^))) @@ -73,15 +69,12 @@ (λ (key value) (set-box! b (cons (cons key value) (unbox b)))))) ;; Setting up -(define (setup-prog! prog #:samplers [samplers #f] #:precondition [precondition 'TRUE]) +(define (setup-prog! prog #:precondition [precondition 'TRUE]) (*start-prog* prog) (rollback-improve!) (timeline-event! 'start) ; This has no associated data, so we don't name it (debug #:from 'progress #:depth 3 "[1/2] Preparing points") - (let* ([samplers (or samplers (map (curryr cons (eval-sampler 'default)) - (program-variables prog)))] - [context (prepare-points prog samplers precondition)]) - (^samplers^ samplers) + (let* ([context (prepare-points prog precondition)]) (^precondition^ precondition) (*pcontext* context) (*analyze-context* context) @@ -134,13 +127,12 @@ (void)) (define (gen-series!) - (when ((flag 'generate 'taylor) #t #f) + (when (flag-set? 'generate 'taylor) (define log! (timeline-event! 'series)) (define series-expansions (apply append - (for/list ([location (^locs^)] - [n (sequence-tail (in-naturals) 1)]) + (for/list ([location (^locs^)] [n (in-naturals 1)]) (debug #:from 'progress #:depth 4 "[" n "/" (length (^locs^)) "] generating series at" location) (taylor-alt (^next-alt^) location)))) (^children^ (append (^children^) series-expansions))) @@ -148,12 +140,11 @@ (void)) (define (gen-rewrites!) - (define alt-rewrite ((flag 'generate 'rr) alt-rewrite-rm alt-rewrite-expression)) + (define alt-rewrite (if (flag-set? 'generate 'rr) alt-rewrite-rm alt-rewrite-expression)) (define log! (timeline-event! 'rewrite)) (define rewritten (apply append - (for/list ([location (^locs^)] - [n (sequence-tail (in-naturals) 1)]) + (for/list ([location (^locs^)] [n (in-naturals 1)]) (debug #:from 'progress #:depth 4 "[" n "/" (length (^locs^)) "] rewriting at" location) (alt-rewrite (alt-add-event (^next-alt^) '(start rm)) #:root location)))) (^children^ @@ -162,11 +153,10 @@ (void)) (define (simplify!) - (when ((flag 'generate 'simplify) #t #f) + (when (flag-set? 'generate 'simplify) (define log! (timeline-event! 'simplify)) (define simplified - (for/list ([child (^children^)] - [n (sequence-tail (in-naturals) 1)]) + (for/list ([child (^children^)] [n (in-naturals 1)]) (debug #:from 'progress #:depth 4 "[" n "/" (length (^children^)) "] simplifiying candidate" child) (with-handlers ([exn:fail? (λ (e) (printf "Failed while simplifying candidate ~a\n" child) (raise e))]) (apply alt-apply child (simplify child))))) @@ -231,20 +221,20 @@ (choose-best-alt!) (debug #:from 'progress #:depth 3 "localizing error") (localize!) - (debug #:from 'progress #:depth 3 "generating series expansions") - (gen-series!) (debug #:from 'progress #:depth 3 "generating rewritten candidates") (gen-rewrites!) + (debug #:from 'progress #:depth 3 "generating series expansions") + (gen-series!) (debug #:from 'progress #:depth 3 "simplifying candidates") (simplify!) (debug #:from 'progress #:depth 3 "adding candidates to table") (finalize-iter!))) (void)) -(define (run-improve prog iters #:samplers [samplers #f] #:get-context [get-context? #f] #:precondition [precondition 'TRUE]) +(define (run-improve prog iters #:get-context [get-context? #f] #:precondition [precondition 'TRUE]) (debug #:from 'progress #:depth 1 "[Phase 1 of 3] Setting up.") - (setup-prog! prog #:samplers samplers #:precondition precondition) - (if ((flag 'setup 'early-exit) (> 0.1 (errors-score (errors (*start-prog*) (*pcontext*)))) #f) + (setup-prog! prog #:precondition precondition) + (if (and (flag-set? 'setup 'early-exit) (< (errors-score (errors (*start-prog*) (*pcontext*))) 0.1)) (let ([init-alt (make-alt (*start-prog*))]) (debug #:from 'progress #:depth 1 "Initial program already accurate, stopping.") (if get-context? @@ -252,36 +242,42 @@ init-alt)) (begin (debug #:from 'progress #:depth 1 "[Phase 2 of 3] Improving.") - (for ([iter (sequence-map add1 (in-range iters))] - #:break (atab-completed? (^table^))) - (debug #:from 'progress #:depth 2 "iteration" iter "/" iters) - (run-iter!)) - (finalize-table!) - (debug #:from 'progress #:depth 1 "[Phase 3 of 3] Extracting.") - (if get-context? - (list (get-final-combination) (*pcontext*)) - (get-final-combination))))) + (let* ([current-alts (atab-all-alts (^table^))] + [new-alt (setup-alt-simplified prog)] + [all-alts (append current-alts (list new-alt))]) + (^table^ (atab-add-altns (^table^) all-alts)) + (for ([iter (in-range iters)] #:break (atab-completed? (^table^))) + (debug #:from 'progress #:depth 2 "iteration" (+ 1 iter) "/" iters) + (run-iter!)) + (finalize-table!) + (debug #:from 'progress #:depth 1 "[Phase 3 of 3] Extracting.") + (if get-context? + (list (get-final-combination) (*pcontext*)) + (get-final-combination)))))) ;; Finishing Herbie (define (finalize-table!) - (when ((flag 'reduce 'post-process) #t #f) + (when (flag-set? 'reduce 'post-process) (^table^ (post-process (^table^) timeline-event!))) (void)) (define (get-final-combination) - (begin0 - (if ((flag 'reduce 'regimes) #t #f) - (let ([log! (timeline-event! 'regimes)]) - (remove-pows (match-let ([`(,tables ,splitpoints) (split-table (^table^))]) - (if (= (length tables) 1) - (extract-alt (car tables)) - (combine-alts splitpoints (map extract-alt tables)))))) - (extract-alt (^table^))) - (timeline-event! 'end))) ; No data here + (define joined-alt + (if (flag-set? 'reduce 'regimes) + (let ([log! (timeline-event! 'regimes)]) + (match-let ([`(,tables ,splitpoints) (split-table (^table^))]) + (if (= (length tables) 1) + (extract-alt (car tables)) + (combine-alts splitpoints (map extract-alt tables))))) + (extract-alt (^table^)))) + (define reduced-alt (remove-pows joined-alt)) + (define cleaned-alt (apply alt-apply reduced-alt (simplify-fp-safe reduced-alt))) + (timeline-event! 'end) + cleaned-alt) ;; Other tools (define (resample!) - (let ([context (prepare-points (*start-prog*) (^samplers^) (^precondition^))]) + (let ([context (prepare-points (*start-prog*) (^precondition^))]) (*pcontext* context) (^table^ (atab-new-context (^table^) context))) (void)) diff --git a/src/old/data.rkt b/src/old/data.rkt index 454bb2f38..b09b44476 100644 --- a/src/old/data.rkt +++ b/src/old/data.rkt @@ -12,17 +12,17 @@ (require math/flonum) -(define (get-report-errs start end target samplers file) +(define (get-report-errs start end target file) (define newcontext (parameterize ([*num-points* 8000]) - (prepare-points (alt-program start) samplers))) + (prepare-points (alt-program start)))) (write-errors file newcontext start end target)) (define (run-test-write-errors tst file) (define start (make-alt (test-program tst))) (define target (make-alt (test-target tst))) - (define end (run-improve (test-program tst) (*num-iterations*) #:samplers (test-samplers tst))) - (get-report-errs start end target (test-samplers tst) file)) + (define end (run-improve (test-program tst) (*num-iterations*))) + (get-report-errs start end target file)) (define (write-errors file pcontext start end target) (parameterize ([*pcontext* pcontext]) diff --git a/src/old/inout.rkt b/src/old/inout.rkt index 4cbac3081..4dc9ea85e 100644 --- a/src/old/inout.rkt +++ b/src/old/inout.rkt @@ -17,15 +17,14 @@ [`(herbie-test . ,_) (let ([tst (parse-test in-expr)]) (set! in-expr (test-program tst)) - (run-improve (test-program tst) (*num-iterations*) - #:samplers (test-samplers tst)))] + (run-improve (test-program tst) (*num-iterations*)))] [`(,(or 'λ 'lambda) ,vars ,body) (run-improve in-expr (*num-iterations*))] [_ (error "did not recognize input")])) (printf "; Input error: ~a\n" (errors-score (alt-errors (make-alt in-expr)))) (printf "; Output error: ~a\n" (errors-score (alt-errors out-alt))) - (define in-prog (eval-prog in-expr mode:fl)) - (define out-prog (eval-prog (alt-program out-alt) mode:fl)) + (define in-prog (eval-prog in-expr 'fl)) + (define out-prog (eval-prog (alt-program out-alt) 'fl)) (when print-points? (for ([(pt ex) (in-pcontext (*pcontext*))]) (let ([in-ans (in-prog pt)] [out-ans (out-prog pt)]) diff --git a/src/plot.rkt b/src/plot.rkt index d71d5f214..f883dcb93 100644 --- a/src/plot.rkt +++ b/src/plot.rkt @@ -92,8 +92,8 @@ ;; If there aren't enough possible big ticks, we fall back to the standard method (append (if (<= min 1.0 max) (list (pre-tick 1.0 #t)) '()) - (if (<= min 0.0 max) (pre-tick 0.0 #t) '()) - (if (<= min -1.0 max) (pre-tick -1.0 #t) '()) + (if (<= min 0.0 max) (list (pre-tick 0.0 #t)) '()) + (if (<= min -1.0 max) (list (pre-tick -1.0 #t)) '()) ((ticks-layout (ticks-scale (linear-ticks #:number 6 #:base 10 #:divisors '(1 2 5)) double-transform)) min max))] [else (define necessary (filter identity (map (curry index-of possible) '(1.0 0.0 -1.0)))) @@ -115,7 +115,7 @@ (define x (if (number? axis) (curryr list-ref axis) - (eval-prog axis mode:fl))) + (eval-prog axis 'fl))) (points (for/list ([pt pts] [err errs]) (vector (x pt) (+ (ulps->bits err) (random) -1/2))) @@ -194,7 +194,7 @@ (define get-coord (if (number? axis) (curryr list-ref axis) - (eval-prog `(λ ,vars ,axis) mode:fl))) + (eval-prog `(λ ,vars ,axis) 'fl))) (define eby (errors-by get-coord errs pts)) (define histogram-f (histogram-function eby #:bin-size bin-size)) (define (avg-fun x) diff --git a/src/points.rkt b/src/points.rkt index cb5a8cb4e..ba238fee6 100644 --- a/src/points.rkt +++ b/src/points.rkt @@ -2,13 +2,81 @@ (require math/flonum) (require math/bigfloat) -(require "float.rkt" "common.rkt" "programs.rkt" "config.rkt" "errors.rkt") +(require "float.rkt" "common.rkt" "programs.rkt" "config.rkt" "errors.rkt" "range-analysis.rkt") (provide *pcontext* in-pcontext mk-pcontext pcontext? prepare-points prepare-points-period make-exacts errors errors-score sorted-context-list sort-context-on-expr random-subsample) +(module+ test + (require rackunit)) + +(define (sample-bounded lo hi #:left-closed? [left-closed? #t] #:right-closed? [right-closed? #t]) + (define lo* (exact->inexact lo)) + (define hi* (exact->inexact hi)) + (cond + [(> lo* hi*) #f] + [(= lo* hi*) + (if (and left-closed? right-closed?) lo* #f)] + [(< lo* hi*) + (define ordinal (- (flonum->ordinal hi*) (flonum->ordinal lo*))) + (define num-bits (ceiling (/ (log ordinal) (log 2)))) + (define random-num (random-exp (inexact->exact num-bits))) + (if (or (and (not left-closed?) (equal? 0 random-num)) + (and (not right-closed?) (equal? ordinal random-num)) + (> random-num ordinal)) + ;; Happens with p < .5 so will not loop forever + (sample-bounded lo hi #:left-closed? left-closed? #:right-closed? right-closed?) + (ordinal->flonum (+ (flonum->ordinal lo*) random-num)))])) + +(module+ test + (check-true (<= 1.0 (sample-bounded 1 2) 2.0)) + (let ([a (sample-bounded 1 2 #:left-closed? #f)]) + (check-true (< 1 a)) + (check-true (<= a 2))) + (check-false (sample-bounded 1 1.0 #:left-closed? #f) "Empty interval due to left openness") + (check-false (sample-bounded 1 1.0 #:right-closed? #f) "Empty interval due to right openness") + (check-false (sample-bounded 1 1.0 #:left-closed? #f #:right-closed? #f) + "Empty interval due to both-openness") + (check-false (sample-bounded 2.0 1.0) "Interval bounds flipped")) + +(define/contract (sample-multi-bounded ranges) + (-> (listof interval?) (or/c flonum? #f)) + (define ordinal-ranges + (for/list ([range ranges]) + (match-define (interval (app exact->inexact lo) (app exact->inexact hi) lo? hi?) range) + (list (flonum->ordinal lo) (flonum->ordinal hi) lo? hi?))) + + (define (points-in-range lo hi lo? hi?) + ;; The `max` handles the case lo > hi and similar + (max 0 (- hi lo (if lo? 0 1) (if hi? -1 0)))) + + (define total-weight + (apply + + (for/list ([range ordinal-ranges]) + (match-define (list lo hi lo? hi?) range) + (points-in-range lo hi lo? hi?)))) + + (match total-weight + [0 #f] + [_ + (define num-bits (ceiling (/ (log total-weight) (log 2)))) + (define sample + (let loop () + (define sample (random-exp (inexact->exact num-bits))) + (if (< sample total-weight) sample (loop)))) + (let loop ([sample sample] [ordinal-ranges ordinal-ranges]) + ;; The `(car)` is guaranteed to succeed by the construction of `sample` + (match-define (list lo hi lo? hi?) (car ordinal-ranges)) + (if (< sample (points-in-range lo hi lo? hi?)) + (ordinal->flonum (+ lo (if lo? 0 1) sample)) + (loop (- sample (points-in-range lo hi lo? hi?)) (cdr ordinal-ranges))))])) + +(module+ test + (check-true (set-member? '(0.0 1.0) (sample-multi-bounded (list (interval 0 0 #t #t) (interval 1 1 #t #t))))) + (check-false (sample-multi-bounded (list (interval 0 0 #t #f) (interval 1 1 #f #t))))) + (define *pcontext* (make-parameter #f)) (struct pcontext (points exacts)) @@ -41,16 +109,12 @@ (define (sorted-context-list context vidx) (let ([p&e (sort (for/list ([(pt ex) (in-pcontext context)]) (cons pt ex)) - < #:key (compose (curryr list-ref vidx) car))]) + prec (*max-mpfr-prec*)) + (raise-herbie-error "Exceeded MPFR precision limit." + #:url "faq.html#mpfr-prec-limit")) + (debug #:from 'points #:depth 4 + "Setting MPFR precision to" prec) (bf-precision prec) - (let ([curr (map f pts)] [good? (map pre pts)]) + (let ([curr (map f pts)] + [good? (map pre pts)]) (if (and prev (andmap (λ (good? old new) (or (not good?) (=-or-nan? old new))) good? prev curr)) (map (λ (good? x) (if good? x +nan.0)) good? curr) (loop (+ prec (*precision-step*)) curr)))))) +; warning: this will start at whatever precision exacts happens to be at (define (make-exacts prog pts precondition) (define n (length pts)) - (let loop ([n* 16]) ; 16 is arbitrary; *num-points* should be n* times a power of 2 - (cond - [(>= n* n) - (make-exacts* prog pts precondition)] - [else - (make-exacts* prog (select-every (round (/ n n*)) pts) precondition) - (loop (* n* 2))]))) + (let loop ([nth (floor (/ n 16))]) + (if (< nth 2) + (begin + (debug #:from 'points #:depth 4 + "Computing exacts for" n "points") + (make-exacts* prog pts precondition)) + (begin + (debug #:from 'points #:depth 4 + "Computing exacts on every" nth "of" n + "points to ramp up precision") + (make-exacts* prog (select-every nth pts) precondition) + (loop (floor (/ nth 2))))))) (define (filter-points pts exacts) "Take only the points for which the exact value is normal, and the point is normal" (reap (sow) (for ([pt pts] [exact exacts]) - (when (and (ordinary-float? exact) (andmap ordinary-float? pt)) + (when (and (ordinary-value? exact) (andmap ordinary-value? pt)) (sow pt))))) (define (filter-exacts pts exacts) "Take only the exacts for which the exact value is normal, and the point is normal" (reap (sow) (for ([pt pts] [exact exacts]) - (when (and (ordinary-float? exact) (andmap ordinary-float? pt)) - (sow exact))))) + (when (and (ordinary-value? exact) (andmap ordinary-value? pt)) + (sow exact))))) + +(define (extract-sampled-points allvars precondition) + (match precondition + [`(== ,(? variable? var) ,(? constant? val)) + (if (set=? (list var) allvars) + (list (list val)) + #f)] + [`(and (== ,(? variable? vars) ,(? constant? vals)) ...) + (if (set=? vars allvars) + (list (map (curry dict-ref (map cons vars vals)) allvars)) + #f)] + [`(or (== ,(? variable? varss) ,(? constant? valss)) ...) + (define pts + (for/list ([var varss] [val valss]) + (if (set=? (list var) allvars) + (list val) + #f))) + (and (andmap identity pts) pts)] + [`(or (and (== ,(? variable? varss) ,(? constant? valss)) ...) ...) + (define pts + (for/list ([vars varss] [vals valss]) + (if (set=? vars allvars) + (map (curry dict-ref (map cons vars vals)) allvars) + #f))) + (and (andmap identity pts) pts)] + [`FALSE '()] + [_ #f])) ; These definitions in place, we finally generate the points. -(define (prepare-points prog samplers precondition) +(define (prepare-points prog precondition) "Given a program, return two lists: a list of input points (each a list of flonums) and a list of exact values for those points (each a flonum)" - ; First, we generate points; + + (define range-table (condition->range-table precondition)) + + (define sampler + (match (extract-sampled-points (program-variables prog) precondition) + [(list pts ...) + (let ([l (length pts)]) (λ () (list-ref pts (random l))))] + [#f + (for ([var (program-variables prog)] + #:unless (range-table-ref range-table var)) + (raise-herbie-error "No valid values of variable ~a" var + #:url "faq.html#no-valid-values")) + (λ () + (for/list ([var (program-variables prog)]) + (match (range-table-ref range-table var) + [(interval lo hi lo? hi?) + (sample-bounded lo hi #:left-closed? lo? #:right-closed? hi?)] + [(list (? interval? ivals) ...) + (sample-multi-bounded ivals)])))])) + (let loop ([pts '()] [exs '()] [num-loops 0]) - (cond [(> num-loops 200) - (raise-herbie-error "Cannot sample enough valid points." #:url "faq.html#sample-valid-points")] - [(>= (length pts) (*num-points*)) - (mk-pcontext (take pts (*num-points*)) (take exs (*num-points*)))] - [#t (let* ([num (- (*num-points*) (length pts))] - [pts1 - (for/list ([n (in-range num)]) - (for/list ([rec samplers]) (sample (cdr rec))))] - [exs1 (make-exacts prog pts1 precondition)] - ; Then, we remove the points for which the answers - ; are not representable - [pts* (filter-points pts1 exs1)] - [exs* (filter-exacts pts1 exs1)]) - (loop (append pts* pts) (append exs* exs) (+ 1 num-loops)))]))) + (let ([npts (length pts)]) + (cond [(> num-loops 200) + (raise-herbie-error "Cannot sample enough valid points." + #:url "faq.html#sample-valid-points")] + [(>= npts (*num-points*)) + (begin + (debug #:from 'points #:tag 'exit #:depth 4 + "Sampled" npts "points with exact outputs") + (mk-pcontext (take pts (*num-points*)) + (take exs (*num-points*))))] + [#t + (let* (; pad to avoid repeatedly trying to get last point + [num (max 4 (- (*num-points*) npts))] + [_ (debug #:from 'points #:depth 4 + "Sampling" num "additional inputs," + "on iter" num-loops "have" npts "/" (*num-points*))] + [pts1 (for/list ([n (in-range num)]) (sampler))] + [exs1 (make-exacts prog pts1 precondition)] + [_ (debug #:from 'points #:depth 4 + "Filtering points with unrepresentable outputs")] + [pts* (filter-points pts1 exs1)] + [exs* (filter-exacts pts1 exs1)]) + ; keep iterating till we get at least *num-points* + (loop (append pts* pts) (append exs* exs) (+ 1 num-loops)))])))) (define (prepare-points-period prog periods) (let* ([pts (make-period-points (*num-points*) periods)] @@ -140,7 +269,7 @@ (mk-pcontext pts* exacts*))) (define (errors prog pcontext) - (let ([fn (eval-prog prog mode:fl)] + (let ([fn (eval-prog prog 'fl)] [max-ulps (expt 2 (*bit-width*))]) (for/list ([(point exact) (in-pcontext pcontext)]) (let ([out (fn point)]) @@ -150,9 +279,9 @@ max-ulps)))))) (define (errors-score e) - (let-values ([(reals unreals) (partition ordinary-float? e)]) - (if ((flag 'reduce 'avg-error) #f #t) - (apply max (map ulps->bits reals)) + (let-values ([(reals unreals) (partition ordinary-value? e)]) + (if (flag-set? 'reduce 'avg-error) (/ (+ (apply + (map ulps->bits reals)) (* (*bit-width*) (length unreals))) - (length e))))) + (length e)) + (apply max (map ulps->bits reals))))) diff --git a/src/programs.rkt b/src/programs.rkt index 1dcc74967..c3ec24fb0 100644 --- a/src/programs.rkt +++ b/src/programs.rkt @@ -1,229 +1,120 @@ #lang racket (require math/bigfloat math/flonum) -(require "common.rkt" "syntax/syntax.rkt" "errors.rkt") +(require "common.rkt" "syntax/syntax.rkt" "errors.rkt" "bigcomplex.rkt") + +(module+ test (require rackunit)) (provide (all-from-out "syntax/syntax.rkt") - location-induct program-induct expression-induct location-hash + program-body program-variables ->flonum ->bf + replace-leaves location-hash + location? expr? location-do location-get location-parent location-sibling - eval-prog replace-subexpr + eval-prog compile expression-cost program-cost free-variables unused-variables replace-expression - assert-expression! assert-program! eval-exact eval-const-expr desugar-program expr->prog) -(define (location-induct - prog - #:toplevel [toplevel (λ (expr location) expr)] #:constant [constant (λ (c location) c)] - #:variable [variable (λ (x location) x)] #:primitive [primitive (λ (list location) list)] - #:symbol [symbol-table (λ (sym location) sym)] #:predicate [predicate (λ (pred loc) pred)]) - - (define (inductor prog location) - (cond - [(constant? prog) - (constant prog (reverse location))] - [(variable? prog) - (variable prog (reverse location))] - [(and (list? prog) (memq (car prog) '(λ lambda))) - (let ([body* (inductor (program-body prog) (cons 2 location))]) - (toplevel `(λ ,(program-variables prog) ,body*) (reverse location)))] - [(and (list? prog) (memq (car prog) predicates)) - (predicate (cons (symbol-table (car prog) (reverse (cons 0 location))) - (for/list ([idx (in-naturals)] [arg prog] #:when (> idx 0)) - (inductor arg (cons idx location)))) - (reverse location))] - [(list? prog) - (primitive (cons (symbol-table (car prog) (reverse (cons 0 location))) - (for/list ([idx (in-naturals)] [arg prog] #:when (> idx 0)) - (inductor arg (cons idx location)))) - (reverse location))])) - (inductor prog '())) - -(define (location-hash prog) - (define expr->locs (make-hash)) +(define expr? (or/c list? symbol? number?)) + +(define location? (listof natural-number/c)) + +;; Programs are just lambda expressions + +(define/contract (program-body prog) + (-> expr? expr?) + (match-define (list (or 'lambda 'λ) (list vars ...) body) prog) + body) + +(define/contract (program-variables prog) + (-> expr? (listof symbol?)) + (match-define (list (or 'lambda 'λ) (list vars ...) body) prog) + vars) + +;; Converting constants +(define/contract (->flonum x) + (-> any/c (or/c flonum? complex? boolean?)) + (define convert + (if (flag-set? 'precision 'double) + real->double-flonum + real->single-flonum)) + (cond + [(real? x) (convert x)] + [(bigfloat? x) (convert (bigfloat->flonum x))] + [(bigcomplex? x) + (make-rectangular (->flonum (bigcomplex-re x)) + (->flonum (bigcomplex-im x)))] + [(and (symbol? x) (constant? x)) + (->flonum ((constant-info x 'fl)))] + [else x])) + +(define (->bf x) + (cond + [(real? x) (bf x)] + [(bigfloat? x) x] + [(complex? x) + (bigcomplex (->bf (real-part x)) (->bf (imag-part x)))] + [(constant? x) ((constant-info x 'bf))] + [else x])) + +(define/contract (location-hash prog) + (-> expr? (hash/c expr? (listof location?))) + (define hash (make-hash)) (define (save expr loc) - (hash-update! expr->locs expr (curry cons loc) '()) - expr) - - (location-induct prog #:constant save #:variable save #:primitive save) - expr->locs) - -(define (expression-induct - expr vars - #:toplevel [toplevel identity] #:constant [constant identity] - #:variable [variable identity] #:primitive [primitive identity] - #:symbol [symbol-table identity] #:predicate [predicate identity]) - (program-body (program-induct - `(λ ,vars ,expr) - #:toplevel toplevel #:constant constant - #:variable variable #:primitive primitive - #:symbol symbol-table #:predicate predicate))) - -(define (program-induct - prog - #:toplevel [toplevel identity] #:constant [constant identity] - #:variable [variable identity] #:primitive [primitive identity] - #:symbol [symbol-table identity] #:predicate [predicate identity]) + (hash-update! hash expr (curry cons loc) '())) + + (let loop ([expr prog] [loc '()]) + (match expr + [(list (or 'lambda 'λ) (list vars ...) body) + (loop body (cons 2 loc))] + [(? constant?) (save expr (reverse loc))] + [(? variable?) (save expr (reverse loc))] + [(list op args ...) + (save expr (reverse loc)) + (for ([idx (in-naturals 1)] [arg args]) + (loop arg (cons idx loc)))])) + + hash) + +(define/contract (replace-leaves prog #:constant [constant identity] + #:variable [variable identity] #:symbol [symbol-table identity]) + (->* (expr?) + (#:constant (-> constant? any/c) #:variable (-> variable? any/c) #:symbol (-> operator? any/c)) + any/c) ; Inlined for speed (define (inductor prog) - (cond - [(constant? prog) (constant prog)] - [(variable? prog) (variable prog)] - [(and (list? prog) (memq (car prog) '(λ lambda))) - (let ([body* (inductor (program-body prog))]) - (toplevel `(λ ,(program-variables prog) ,body*)))] - [(and (list? prog) (memq (car prog) predicates)) - (predicate (cons (symbol-table (car prog)) - (map inductor (cdr prog))))] - [(list? prog) - (primitive (cons (symbol-table (car prog)) - (map inductor (cdr prog))))])) + (match prog + [(list (or 'lambda 'λ) (list vars ...) body) + `(λ ,vars ,(inductor body))] + [(? constant?) (constant prog)] + [(? variable?) (variable prog)] + [(list 'if cond ift iff) + `(if ,(inductor cond) ,(inductor ift) ,(inductor iff))] + [(list op args ...) + (cons (symbol-table op) (map inductor args))] + [_ (error (format "Invalid program ~a" prog))])) (inductor prog)) (define (free-variables prog) (match prog - [(? constant?) '()] - [(? variable?) (list prog)] - [`(lambda ,vars ,body) + [(? constant?) '()] + [(? variable?) (list prog)] + [(list (or 'lambda 'λ) vars body) (remove* vars (free-variables body))] - [`(,op ,args ...) ; TODO what if op unbound? - (remove-duplicates (append-map free-variables args))])) + [`(,op ,args ...) + (remove-duplicates (append-map free-variables args))])) (define (unused-variables prog) (remove* (free-variables (program-body prog)) (program-variables prog))) -(define (check-expression* stx vars error!) - (match (or (syntax->list stx) (syntax-e stx)) - [(? constant?) (void)] - [(? variable?) - (unless (set-member? vars (syntax-e stx)) - (error! stx "Unknown variable ~a" (syntax-e stx)))] - [(list (app syntax-e 'let) (app syntax->list (list (app syntax->list (list vars* vals)) ...)) body) - ;; These are unfolded by desugaring - (for ([var vars*] [val vals]) - (unless (identifier? var) - (error! var "Invalid variable name ~a" (syntax-e var))) - (check-expression* val vars error!)) - (check-expression* body (append vars (map syntax-e vars*)) error!)] - [(list (app syntax-e (? (curry set-member? '(+ - * /)))) args ...) - ;; These expand associativity so we don't check the number of arguments - (for ([arg args]) (check-expression* arg vars error!))] - [(list f args ...) - (if (hash-has-key? (*operations*) (syntax->datum f)) - (let ([num-args (list-ref (hash-ref (*operations*) (syntax->datum f)) mode:args)]) - (unless (or (set-member? num-args (length args)) (set-member? num-args '*)) - (error! stx "Operator ~a given ~a arguments (expects ~a)" - (syntax->datum f) (length args) (string-join (map ~a num-args) " or ")))) - (error! stx "Unknown operator ~a" (syntax->datum f))) - (for ([arg args]) (check-expression* arg vars error!))] - [_ (error! stx "Unknown syntax ~a" (syntax->datum stx))])) - -(define (check-property* prop error!) - (unless (identifier? prop) - (error! prop "Invalid property name ~a" (syntax->datum prop))) - (define name (~a (syntax-e prop))) - (unless (equal? (substring name 0 1) ":") - (error! prop "Invalid property name ~a" (syntax->datum prop)))) - -(define (check-properties* props vars error!) - (define prop-dict - (let loop ([props props] [out '()]) - (match props - [(list (? identifier? prop-name) value rest ...) - (check-property* prop-name error!) - (loop rest (cons (cons (syntax-e prop-name) value) out))] - [(list head) - (check-property* head error!) - (error! head "Property ~a has no value" (syntax->datum head))] - [(list) - out]))) - - (when (dict-has-key? prop-dict ':name) - (define name (dict-ref prop-dict ':name)) - (unless (string? (syntax-e name)) - (error! name "Invalid :name ~a; must be a string" (syntax->datum name)))) - - (when (dict-has-key? prop-dict ':description) - (define desc (dict-ref prop-dict ':description)) - (unless (string? (syntax-e desc)) - (error! desc "Invalid :description ~a; must be a string" (syntax->datum desc)))) - - (when (dict-has-key? prop-dict ':cite) - (define cite (dict-ref prop-dict ':cite)) - (unless (list? (syntax-e cite)) - (error! cite "Invalid :cite ~a; must be a list" (syntax->datum cite))) - (when (list? (syntax-e cite)) - (for ([citation (syntax->list cite)] #:unless (identifier? citation)) - (error! citation "Invalid citation ~a; must be a variable name" (syntax->datum citation))))) - - (when (dict-has-key? prop-dict ':pre) - (check-expression* (dict-ref prop-dict ':pre) vars error!)) - - (when (dict-has-key? prop-dict ':target) - (check-expression* (dict-ref prop-dict ':target) vars error!)) - - (when (dict-has-key? prop-dict ':herbie-samplers) - (let ([stx (dict-ref prop-dict ':herbie-samplers)]) - (eprintf "Deprecated :herbie-samplers property used.\n") - (define file - (if (path? (syntax-source stx)) - (let-values ([(base name dir?) (split-path (syntax-source stx))]) - (path->string name)) - (syntax-source stx))) - (eprintf " ~a:~a:~a: Use the :pre property to specify bounds\n" file (or (syntax-line stx) "") - (or (syntax-column stx) (syntax-position stx))) - (eprintf "See for more.\n")))) - -(define (check-program* stx error!) - (match (syntax->list stx) - [(list (app syntax-e 'FPCore) vars props ... body) - (unless (list? (syntax->list vars)) - (error! stx "Invalid arguments list ~a; must be a list" (syntax->datum stx))) - (when (list? (syntax->list vars)) - (for ([var (syntax->list vars)] #:unless (identifier? var)) - (error! stx "Argument ~a is not a variable name" (syntax->datum stx))) - (when (check-duplicate-identifier (syntax->list vars)) - (error! stx "Duplicate argument name ~a" - (syntax->datum (check-duplicate-identifier (syntax->list vars)))))) - (define vars* (if (list? (syntax->datum vars)) (syntax->datum vars) '())) - (check-properties* props vars* error!) - (check-expression* body vars* error!)] - [_ (error! stx "Unknown syntax ~a" (syntax->datum stx))])) - -(define (assert-expression! stx vars) - (define errs - (reap [sow] - (define (error! stx fmt . args) - (sow (cons stx (apply format fmt args)))) - (check-expression* stx vars error!))) - (unless (null? errs) - (raise-herbie-syntax-error "Invalid expression" #:locations errs))) - -(define (assert-program! stx) - (define errs - (reap [sow] - (define (error! stx fmt . args) - (sow (cons stx (apply format fmt args)))) - (check-program* stx error!))) - (unless (null? errs) - (raise-herbie-syntax-error "Invalid program" #:locations errs))) - -(define (replace-expression program from to) - (cond - [(equal? program from) - to] - [(list? program) - (for/list ([subexpr program]) - (replace-expression subexpr from to))] - [else - program])) - -(define (location-do loc prog f) + +(define/contract (location-do loc prog f) + (-> location? expr? (-> expr? expr?) expr?) (cond [(null? loc) (f prog)] @@ -257,26 +148,35 @@ #f])))) (define (eval-prog prog mode) - (let* ([real->precision (if (equal? mode mode:bf) ->bf ->flonum)] - [op->precision (λ (op) (list-ref (hash-ref (*operations*) op) mode))] - [prog* (program-induct prog #:constant real->precision #:symbol op->precision)] - [prog-opt `(λ ,(program-variables prog*) ,(compile (program-body prog*)))] + (let* ([real->precision (if (equal? mode 'bf) ->bf ->flonum)] + [op->precision (λ (op) (operator-info op mode))] ; TODO change use of mode + [body* (replace-leaves (program-body prog) #:constant real->precision #:symbol op->precision)] + [prog-opt `(λ ,(program-variables prog) ,(compile body*))] [fn (eval prog-opt common-eval-ns)]) (lambda (pts) (->flonum (apply fn (map real->precision pts)))))) -;; Does the same thing as the above with mode:bf, but doesn't convert +;; Does the same thing as the above with mode 'bf, but doesn't convert ;; the results back to floats. (define (eval-exact prog) - (let* ([prog* (program-induct prog #:constant ->bf #:symbol real-op->bigfloat-op)] - [prog-opt `(lambda ,(program-variables prog*) ,(compile (program-body prog*)))] + (let* ([body* (replace-leaves (program-body prog) #:constant ->bf #:symbol (curryr operator-info 'bf))] + [prog-opt `(lambda ,(program-variables prog) ,(compile body*))] [fn (eval prog-opt common-eval-ns)]) (lambda (pts) (apply fn (map ->bf pts))))) (define (eval-const-expr expr) - (let* ([expr_bf (expression-induct expr '() #:constant ->bf #:symbol real-op->bigfloat-op)]) - (->flonum (eval expr_bf common-eval-ns)))) + (eval + (replace-leaves + expr + #:constant (λ (x) (if (symbol? x) (->flonum x) x)) + #:symbol (curryr operator-info 'nonffi)) + common-eval-ns)) + +(module+ test + (check-equal? (eval-const-expr '(+ 1 1)) 2) + (check-equal? (eval-const-expr 'PI) pi) + (check-equal? (eval-const-expr '(exp 2)) (exp 2))) ;; To compute the cost of a program, we could use the tree as a ;; whole, but this is inaccurate if the program has many common @@ -309,20 +209,30 @@ (define (expression-cost expr) (for/sum ([step (second (compile expr))]) (if (list? (second step)) - (let ([fn (caadr step)]) - (list-ref (hash-ref (*operations*) fn) mode:cost)) + (operator-info (caadr step) 'cost) 1))) -(define (replace-subexpr prog needle needle*) - `(λ ,(program-variables prog) - ,(replace-expr-subexpr (program-body prog) needle needle*))) +(define/contract (replace-expression haystack needle needle*) + (-> expr? expr? expr? expr?) + (match haystack + [(== needle) needle*] + [(list (or 'lambda 'λ) (list vars ...) body) + `(λ ,vars ,(replace-expression body needle needle*))] + [(list op args ...) + (cons op (map (curryr replace-expression needle needle*) args))] + [x x])) + +(module+ test + (check-equal? (replace-expression '(λ (x) (- x (sin x))) 'x 1) + '(λ (x) (- 1 (sin 1)))) + + (check-equal? + (replace-expression + '(/ (cos (* 2 x)) (* (pow cos 2) (* (fabs (* sin x)) (fabs (* sin x))))) + 'cos + '(/ 1 cos)) + '(/ (cos (* 2 x)) (* (pow (/ 1 cos) 2) (* (fabs (* sin x)) (fabs (* sin x))))))) -(define (replace-expr-subexpr haystack needle needle*) - (cond [(equal? haystack needle) needle*] - [(list? haystack) - (cons (car haystack) (map (curryr replace-expr-subexpr needle* needle) - (cdr haystack)))] - [#t haystack])) (define (unfold-let expr) (match expr @@ -339,6 +249,8 @@ (list op (expand-associativity (cons op a)) (expand-associativity b))] + [(list '/ a) + (list '/ 1 (expand-associativity a))] [(list op a ...) (cons op (map expand-associativity a))] [_ diff --git a/src/range-analysis.rkt b/src/range-analysis.rkt index 5d15d36c2..822503b82 100644 --- a/src/range-analysis.rkt +++ b/src/range-analysis.rkt @@ -105,8 +105,72 @@ (check-equal? (interval-invert (interval 1 2 #t #f)) (interval -inf.0 +inf.0 #f #f)) (check-equal? (interval-invert #f) (interval -inf.0 +inf.0 #f #f))) -(define (make-range-table x intvl) - (make-hash (list (cons x intvl)))) + +;; An `intervals` is a list of intervals: nonoverlapping, sorted, and with non-empty gaps between each + +(define/contract (intervals-normalize ivals) + (-> (listof interval?) (listof interval?)) + ;; Could be faster, but not asymptotically so + (foldl intervals-union (list) (map list ivals))) + +(define/contract (intervals-union ivals1 ivals2) + (-> (listof interval?) (listof interval?) (listof interval?)) + (if (or (null? ivals1) (null? ivals2)) + (append ivals1 ivals2) ; Picks out the non-null one + (match-let ([(interval lo1 hi1 lo1? hi1?) (car ivals1)] + [(interval lo2 hi2 lo2? hi2?) (car ivals2)]) + (cond + [(or (< hi1 lo2) (and (= hi1 lo2) (not hi1?) (not lo2?))) + (cons (car ivals1) (intervals-union (cdr ivals1) ivals2))] + [(or (< hi2 lo1) (and (= hi2 lo1) (not hi2?) (not lo1?))) + (cons (car ivals2) (intervals-union ivals1 (cdr ivals2)))] + [else + ;; The termination argument here is that we are now taking the union of one fewer things, total + ;; It is valid because here we are guaranteed that the intervals have no gap between them + (intervals-union + (list (interval-union (car ivals1) (car ivals2))) + (intervals-union (cdr ivals1) (cdr ivals2)))])))) + +(define/contract (intervals-intersect ivals1 ivals2) + (-> (listof interval?) (listof interval?) (listof interval?)) + (if (or (null? ivals1) (null? ivals2)) + '() + (match-let ([(interval lo1 hi1 lo1? hi1?) (car ivals1)] + [(interval lo2 hi2 lo2? hi2?) (car ivals2)]) + (define intersection (interval-intersect (car ivals1) (car ivals2))) + (define rest + (cond + [(< hi1 hi2) (intervals-intersect (cdr ivals1) ivals2)] + [(= hi1 hi2) (intervals-intersect (cdr ivals1) (cdr ivals2))] + [else (intervals-intersect ivals1 (cdr ivals2))])) + (if intersection + (cons intersection rest) + rest)))) + +(define/contract (intervals-invert ivals) + (-> (listof interval?) (listof interval?)) + (append + (match ivals + [(list) + (list (interval -inf.0 +inf.0 #f #f))] + [(list (interval lo hi lo? hi?) rest ...) + (if (= lo -inf.0) + '() + (list (interval -inf.0 lo #f (not lo?))))]) + (let loop ([ivals ivals]) + (match ivals + [(list) '()] + [(list (interval lo hi lo? hi?)) + (if (= hi +inf.0) + '() + (list (interval hi +inf.0 (not hi?) #f)))] + [(list (interval _ lo _ lo?) (and next (interval hi _ hi? _)) rest ...) + (cons (interval lo hi (not lo?) (not hi?)) (loop (cons next rest)))])))) + + + +(define (make-range-table x . intvls) + (make-hash (list (cons x (intervals-normalize (filter identity intvls)))))) (define (make-empty-range-table) (make-hash)) @@ -116,21 +180,23 @@ #f) (define (range-table-ref rt x) - (and rt (hash-ref rt x (interval -inf.0 +inf.0 #f #f)))) + (if rt + (hash-ref rt x (list (interval -inf.0 +inf.0 #f #f))) + '())) (module+ test (define rt-x1 (make-range-table 'x (interval 1 3 #t #t))) (define rt-x2 (make-range-table 'x (interval 2 4 #t #t))) - (define rt-x3 (make-range-table 'x(interval -3 -1 #f #f))) + (define rt-x3 (make-range-table 'x (interval -3 -1 #f #f))) (define rt-y2 (make-range-table 'y (interval 2 4 #t #t))) (define rt-x-neginf (make-range-table 'x (interval -inf.0 1 #f #t))) (define rt-x-posinf (make-range-table 'x (interval 1 +inf.0 #t #f))) (define rt-x1y1 (make-range-table 'x (interval 1 3 #t #t))) - (hash-set! rt-x1y1 'y (interval 2 4 #t #t)) + (hash-set! rt-x1y1 'y (list (interval 2 4 #t #t))) - (check-equal? (interval -inf.0 +inf.0 #f #f) (range-table-ref rt-x1 'y)) + (check-equal? (list (interval -inf.0 +inf.0 #f #f)) (range-table-ref rt-x1 'y)) (check-equal? (range-table-ref rt-x2 'x) (range-table-ref rt-y2 'y)) - (check-equal? (range-table-ref #f 'x) #f)) + (check-equal? (range-table-ref #f 'x) '())) (define (range-table-intersect table1 table2) (cond @@ -140,18 +206,20 @@ (define new-range-table (make-hash)) (for ([key1 (hash-keys table1)]) (if (hash-has-key? table2 key1) - (hash-set! new-range-table key1 (interval-intersect (hash-ref table1 key1) (hash-ref table2 key1))) + (hash-set! new-range-table key1 (intervals-intersect (hash-ref table1 key1) (hash-ref table2 key1))) (hash-set! new-range-table key1 (hash-ref table1 key1)))) (for ([key2 (hash-keys table2)] #:unless (hash-has-key? new-range-table key2)) (hash-set! new-range-table key2 (hash-ref table2 key2))) - new-range-table])) + (if (ormap (curry null?) (hash-values new-range-table)) + #f + new-range-table)])) (module+ test - (check-equal? (interval 2 3 #t #t) (hash-ref (range-table-intersect rt-x1 rt-x2) 'x)) - (check-equal? (interval 1 3 #t #t) (hash-ref (range-table-intersect rt-x1 rt-y2) 'x)) - (check-equal? (interval 2 4 #t #t) (hash-ref (range-table-intersect rt-x1 rt-y2) 'y)) + (check-equal? (list (interval 2 3 #t #t)) (hash-ref (range-table-intersect rt-x1 rt-x2) 'x)) + (check-equal? (list (interval 1 3 #t #t)) (hash-ref (range-table-intersect rt-x1 rt-y2) 'x)) + (check-equal? (list (interval 2 4 #t #t)) (hash-ref (range-table-intersect rt-x1 rt-y2) 'y)) (check-equal? #f (range-table-intersect rt-x1 #f)) - (check-equal? #f (hash-ref (range-table-intersect rt-x3 rt-x1) 'x))) + (check-equal? #f (range-table-intersect rt-x3 rt-x1))) (define (range-table-union table1 table2) (cond @@ -160,19 +228,19 @@ [else (define new-range-table (make-hash)) (for ([key1 (hash-keys table1)] #:when (hash-has-key? table2 key1)) - (hash-set! new-range-table key1 (interval-union (hash-ref table1 key1) (hash-ref table2 key1)))) + (hash-set! new-range-table key1 (intervals-union (hash-ref table1 key1) (hash-ref table2 key1)))) new-range-table])) (module+ test - (check-equal? (interval 1 4 #t #t) (hash-ref (range-table-union rt-x1 rt-x2) 'x)) + (check-equal? (list (interval 1 4 #t #t)) (hash-ref (range-table-union rt-x1 rt-x2) 'x)) (check-true (hash-empty? (range-table-union rt-x1 rt-y2))) (check-equal? rt-x1 (range-table-union rt-x1 #f))) (define (range-table-invert table) (cond - [(and table (= (count identity (hash-values table)) 1)) - (match-define (list (cons var itvl)) (filter cdr (hash->list table))) - (make-range-table var (interval-invert itvl))] + [(and table (= (length (hash-values table)) 1)) + (match-define (list (list var itvls ...)) (hash->list table)) + (apply make-range-table var (intervals-invert itvls))] [else (make-empty-range-table)])) @@ -209,16 +277,22 @@ (make-range-table var (make-interval (- num) num #f #f))] [`(<= ,(? variable? var) ,(? number? num)) (make-range-table var (make-interval -inf.0 num #f #t))] - [`(,(or '<= '==) (fabs ,(? variable? var)) ,(? number? num)) + [`(<= (fabs ,(? variable? var)) ,(? number? num)) (make-range-table var (make-interval (- num) num #t #t))] + [`(== (fabs ,(? variable? var)) ,(? number? num)) + (make-range-table var (make-interval (- num) (- num) #t #t) (make-interval num num #t #t))] [`(> ,(? variable? var) ,(? number? num)) (make-range-table var (make-interval num +inf.0 #f #f))] - [`(,(or '> '>=) (fabs ,(? variable? var)) ,(? number? num)) - (make-empty-range-table)] + [`(> (fabs ,(? variable? var)) ,(? number? num)) + (make-range-table var (make-interval num +inf.0 #f #f) (make-interval -inf.0 (- num) #f #f))] [`(>= ,(? variable? var) ,(? number? num)) (make-range-table var (make-interval num +inf.0 #t #f))] + [`(>= (fabs ,(? variable? var)) ,(? number? num)) + (make-range-table var (make-interval num +inf.0 #t #f) (make-interval -inf.0 (- num) #f #t))] [(list (and (or '< '<= '== '>= '>) cmp) (? number? num) var) ; don't check for variable? here b/c fabs (condition->range-table (list (flip-cmp cmp) var num))] + [(list (and (or '< '<= '== '>= '>) cmp) _ _) ; handle case of complex expressions + (make-empty-range-table)] [(list (and (or '< '<= '> '>=) cmp) exprs ...) (if (not (equal? (filter number? exprs) (sort (filter number? exprs) (parse-cmp cmp)))) #f @@ -244,6 +318,8 @@ (map (lambda (x) (make-range-table x (make-interval num num #t #t))) (filter variable? exprs))) (make-null-range-table))] + [(list '!= expr1 expr2) ; == and != are not inverses for more than two arguments + (range-table-invert (condition->range-table `(== ,expr1 ,expr2)))] [`(and ,conds ...) (foldl range-table-intersect (make-empty-range-table) (map condition->range-table conds))] @@ -269,34 +345,34 @@ (cons last (loop rest last))]))) (module+ test - (check-equal? (condition->range-table '(== 1 x 1 1 y 1 1 1)) (make-hash (list (cons 'x (interval 1 1 #t #t)) (cons 'y (interval 1 1 #t #t))))) + (check-equal? (condition->range-table '(== 1 x 1 1 y 1 1 1)) (make-hash (list (list 'x (interval 1 1 #t #t)) (list 'y (interval 1 1 #t #t))))) (check-equal? (condition->range-table '(== 1 x 1 2 y 1 1 1)) #f) - (check-equal? (condition->range-table '(> x 1)) (make-hash (list (cons 'x (interval 1 +inf.0 #f #f))))) - (check-equal? (condition->range-table '(>= x 1)) (make-hash (list (cons 'x (interval 1 +inf.0 #t #f))))) - (check-equal? (condition->range-table '(<= x 1)) (make-hash (list (cons 'x (interval -inf.0 1 #f #t))))) - (check-equal? (condition->range-table '(< x 1)) (make-hash (list (cons 'x (interval -inf.0 1 #f #f))))) + (check-equal? (condition->range-table '(> x 1)) (make-hash (list (list 'x (interval 1 +inf.0 #f #f))))) + (check-equal? (condition->range-table '(>= x 1)) (make-hash (list (list 'x (interval 1 +inf.0 #t #f))))) + (check-equal? (condition->range-table '(<= x 1)) (make-hash (list (list 'x (interval -inf.0 1 #f #t))))) + (check-equal? (condition->range-table '(< x 1)) (make-hash (list (list 'x (interval -inf.0 1 #f #f))))) (check-equal? (condition->range-table '(< 1 1)) #f) (check-equal? (condition->range-table '(< 1 2)) (make-hash)) - (check-equal? (condition->range-table '(< x 1)) (make-hash (list (cons 'x (interval -inf.0 1 #f #f))))) - (check-equal? (condition->range-table '(< x y 2)) (make-hash (list (cons 'x (interval -inf.0 2 #f #f)) (cons 'y (interval -inf.0 2 #f #f))))) - (check-equal? (condition->range-table '(< 1 x y 2)) (make-hash (list (cons 'x (interval 1.0 2.0 #f #f)) (cons 'y (interval 1.0 2.0 #f #f))))) - (check-equal? (range-table-ref (condition->range-table '(< 1 2 3)) 'x) (interval -inf.0 +inf.0 #f #f)) - (check-equal? (range-table-ref (condition->range-table '(< 10 2 3)) 'x) #f) - (check-equal? (range-table-ref (condition->range-table '(< 0 x 4 3)) 'x) #f) - (check-equal? (condition->range-table '(and (< x 1) (> x -1))) (make-hash (list (cons 'x (interval -1.0 1.0 #f #f))))) - (check-equal? (condition->range-table '(or (< x 1) (> x -1))) (make-hash (list (cons 'x (interval -inf.0 +inf.0 #f #f))))) - (check-equal? (condition->range-table '(or (< x -1) (> x 1))) (make-hash (list (cons 'x (interval -inf.0 +inf.0 #f #f))))) - (check-equal? (condition->range-table '(or (< x 1) (< x 2) (> x -1))) (make-hash (list (cons 'x (interval -inf.0 +inf.0 #f #f))))) - (check-equal? (condition->range-table '(and (< x 1) (< x 2) (> x -1))) (make-hash (list (cons 'x (interval -1.0 1.0 #f #f))))) - (check-equal? (condition->range-table '(not (< x 1))) (make-hash (list (cons 'x (interval 1 +inf.0 #t #f))))) - (check-equal? (condition->range-table '(not (> x 1))) (make-hash (list (cons 'x (interval -inf.0 1 #f #t))))) - (check-equal? (condition->range-table '(and (not (> x 1)) (not (< x -2)))) (make-hash (list (cons 'x (interval -2.0 1.0 #t #t))))) + (check-equal? (condition->range-table '(< x 1)) (make-hash (list (list 'x (interval -inf.0 1 #f #f))))) + (check-equal? (condition->range-table '(< x y 2)) (make-hash (list (list 'x (interval -inf.0 2 #f #f)) (list 'y (interval -inf.0 2 #f #f))))) + (check-equal? (condition->range-table '(< 1 x y 2)) (make-hash (list (list 'x (interval 1.0 2.0 #f #f)) (list 'y (interval 1.0 2.0 #f #f))))) + (check-equal? (range-table-ref (condition->range-table '(< 1 2 3)) 'x) (list (interval -inf.0 +inf.0 #f #f))) + (check-equal? (range-table-ref (condition->range-table '(< 10 2 3)) 'x) '()) + (check-equal? (range-table-ref (condition->range-table '(< 0 x 4 3)) 'x) '()) + (check-equal? (condition->range-table '(and (< x 1) (> x -1))) (make-hash (list (list 'x (interval -1.0 1.0 #f #f))))) + (check-equal? (condition->range-table '(or (< x 1) (> x -1))) (make-hash (list (list 'x (interval -inf.0 +inf.0 #f #f))))) + (check-equal? (condition->range-table '(or (< x -1) (> x 1))) (make-hash (list (list 'x (interval -inf.0 -1 #f #f) (interval 1 +inf.0 #f #f))))) + (check-equal? (condition->range-table '(or (< x 1) (< x 2) (> x -1))) (make-hash (list (list 'x (interval -inf.0 +inf.0 #f #f))))) + (check-equal? (condition->range-table '(and (< x 1) (< x 2) (> x -1))) (make-hash (list (list 'x (interval -1.0 1.0 #f #f))))) + (check-equal? (condition->range-table '(not (< x 1))) (make-hash (list (list 'x (interval 1 +inf.0 #t #f))))) + (check-equal? (condition->range-table '(not (> x 1))) (make-hash (list (list 'x (interval -inf.0 1 #f #t))))) + (check-equal? (condition->range-table '(and (not (> x 1)) (not (< x -2)))) (make-hash (list (list 'x (interval -2.0 1.0 #t #t))))) ; this following two test tells us that we could have two representations of empty range-table ; therefore we use range-table-ref to hide the internal representation of range-table - (check-equal? (range-table-ref (condition->range-table '(or (not (> x 1)) (not (< x -2)))) 'x) (interval -inf.0 +inf.0 #f #f)) - (check-equal? (range-table-ref (condition->range-table '(not (> x +inf.0))) 'x) (interval -inf.0 +inf.0 #f #f)) - (check-equal? (condition->range-table '(< (fabs x) 3)) (make-hash (list (cons 'x (interval -3 3 #f #f))))) - (check-equal? (condition->range-table '(<= (fabs x) 3)) (make-hash (list (cons 'x (interval -3 3 #t #t))))) - (check-equal? (condition->range-table '(> (fabs x) 3)) (make-hash)) - (check-equal? (condition->range-table '(>= 3 (fabs x))) (make-hash (list (cons 'x (interval -3 3 #t #t))))) - (check-equal? (condition->range-table '(<= 3 (fabs x))) (make-hash))) + (check-equal? (range-table-ref (condition->range-table '(or (not (> x 1)) (not (< x -2)))) 'x) (list (interval -inf.0 +inf.0 #f #f))) + (check-equal? (range-table-ref (condition->range-table '(not (> x +inf.0))) 'x) (list (interval -inf.0 +inf.0 #f #f))) + (check-equal? (condition->range-table '(< (fabs x) 3)) (make-hash (list (list 'x (interval -3 3 #f #f))))) + (check-equal? (condition->range-table '(<= (fabs x) 3)) (make-hash (list (list 'x (interval -3 3 #t #t))))) + (check-equal? (condition->range-table '(> (fabs x) 3)) (make-hash (list (list 'x (interval -inf.0 -3 #f #f) (interval 3 +inf.0 #f #f))))) + (check-equal? (condition->range-table '(>= 3 (fabs x))) (make-hash (list (list 'x (interval -3 3 #t #t))))) + (check-equal? (condition->range-table '(<= 3 (fabs x))) (make-hash (list (list 'x (interval -inf.0 -3 #f #t) (interval 3 +inf.0 #t #f)))))) diff --git a/src/reports/core2js.rkt b/src/reports/core2js.rkt new file mode 100644 index 000000000..e23c9a1ee --- /dev/null +++ b/src/reports/core2js.rkt @@ -0,0 +1,186 @@ +#lang racket + +(require "fpcore-common.rkt" "fpcore.rkt") +(provide compile-program) + +(define (fix-name name) + (string-join + (for/list ([char (~a name)]) + (if (regexp-match #rx"[a-zA-Z0-9_]" (string char)) + (string char) + (format "_~a_" (char->integer char)))) + "")) + +(define/match (constant->js c) + [('E) "math.E"] + [('LOG2E) "math.LOG2E"] + [('LOG10E) "math.LOG10E"] + [('LN2) "math.LN2"] + [('LN10) "math.LN10"] + [('PI) "math.pi"] + [('PI_2) "(math.pi/2)"] + [('PI_4) "(math.pi/4)"] + [('1_PI) "(1/math.pi)"] + [('2_PI) "(2/math.pi)"] + [('2_SQRTPI) "(2/math.sqrt(math.pi))"] + [('SQRT2) "math.SQRT2"] + [('SQRT1_2) "(1/math.SQRT2)"] + [('MAXFLOAT) "Number.MAX_VALUE"] + [('TRUE) "true"] + [('FALSE) "false"] + [('INFINITY) "Infinity"] + [('NAN) "math.NaN"] + [(_) (error 'constant->js "Unsupported constant ~a" c)]) + +(define/match (operator->js op) + [((or '== '+ '- '* '/ '< '> '<= '>=)) (format "(~a ~a ~a)" "~a" op "~a")] + [('and) "~a && ~a"] + [('or) "~a || ~a"] + [('not) "!~a"] + [('fabs) "math.abs(~a)"] + [('exp) "math.exp(~a)"] + [('exp2) "math.pow(2, ~a)"] + [('expm1) "math.expm1(~a)"] + [('log) "math.log(~a)"] + [('log10) "math.log10(~a)"] + [('log2) "math.log2(~a)"] + [('log1p) "math.log1p(~a)"] + [('logb) "math.floor(math.log2(math.abs(~a)))"] + [('pow) "math.pow(~a, ~a)"] + [('sqrt) "math.sqrt(~a)"] + [('cbrt) "math.cbrt(~a)"] + [('hypot) "math.hypot(~a, ~a)"] + [('sin) "math.sin(~a)"] + [('cos) "math.cos(~a)"] + [('tan) "math.tan(~a)"] + [('asin) "math.asin(~a)"] + [('acos) "math.acos(~a)"] + [('atan) "math.atan(~a)"] + [('atan2) "math.atan2(~a, ~a)"] + [('sinh) "math.sinh(~a)"] + [('cosh) "math.cosh(~a)"] + [('tanh) "math.tanh(~a)"] + [('asinh) "math.asinh(~a)"] + [('acosh) "math.acosh(~a)"] + [('atanh) "math.atanh(~a)"] + [('erf) "math.erf(~a)"] + [('erfc) "1 - math.erf(~a)"] ;; TODO: This implementation has large error for large inputs + [('tgamma) "math.gamma(~a)"] + [('lgamma) "math.log(math.gamma(~a))"] + [('ceil) "math.ceil(~a)"] + [('floor) "math.floor(~a)"] + [('remainder) "math.mod(~a, ~a)"] + [('fmax) "math.max(~a, ~a)"] + [('fmin) "math.min(~a, ~a)"] + [('fdim) "math.max(0, ~a - ~a)"] + [('copysign) "math.abs(~a) * math.sign(~a)"] + [('trunc) "math.fix(~a)"] + [('round) "math.round(~a)"] + [('isinf) "(math.abs(~a) === Number.POSITIVE_INFINITY)"] + [('isnan) "isNaN(~a)"] + [(_) (error 'operator->js "Unsupported operation ~a" op)]) + +(define (application->js type operator args) + (if (and (eq? operator '-) (= (length args) 1)) + (format "(- ~a)" (car args)) + (apply format (operator->js operator) args))) + +(define *names* (make-parameter (mutable-set))) + +(define (gensym name) + (define prefixed + (filter (λ (x) (string-prefix? (~a x) (~a name))) (set->list (*names*)))) + (define options + (cons name (for/list ([_ prefixed] [i (in-naturals)]) + (string->symbol (format "~a_~a" name (+ i 1)))))) + (define name* + (car (set-subtract options prefixed))) + (set-add! (*names*) name*) + name*) + +(define (expr->js expr #:names [names #hash()] #:type [type 'binary64] #:indent [indent "\t"]) + ;; Takes in an expression. Returns an expression and a new set of names + (match expr + [`(let ([,vars ,vals] ...) ,body) + (define vars* (map gensym vars)) + (for ([var vars] [var* vars*] [val vals]) + (printf "~avar ~a = ~a;\n" indent (fix-name var*) + (expr->js val #:names names #:type type #:indent indent))) + (define names* + (for/fold ([names* names]) ([var vars] [var* vars*]) + (dict-set names* var var*))) + (expr->js body #:names names* #:type type #:indent indent)] + [`(if ,cond ,ift ,iff) + (define test (expr->js cond #:names names #:type type #:indent indent)) + (define outvar (gensym 'temp)) + (printf "~avar ~a;\n" indent (fix-name outvar)) + (printf "~aif (~a) {\n" indent test) + (printf "~a\t~a = ~a;\n" indent (fix-name outvar) + (expr->js ift #:names names #:type type #:indent (format "~a\t" indent))) + (printf "~a} else {\n" indent) + (printf "~a\t~a = ~a;\n" indent (fix-name outvar) + (expr->js iff #:names names #:type type #:indent (format "~a\t" indent))) + (printf "~a}\n" indent) + (fix-name outvar)] + [`(while ,cond ([,vars ,inits ,updates] ...) ,retexpr) + (define vars* (map gensym vars)) + (for ([var vars] [var* vars*] [val inits]) + (printf "~avar ~a = ~a;\n" indent (fix-name var*) + (expr->js val #:names names #:type type #:indent indent))) + (define names* + (for/fold ([names* names]) ([var vars] [var* vars*]) + (dict-set names* var var*))) + (define test-var (fix-name (gensym 'test))) + (printf "~avar ~a = ~a\n" indent test-var + (expr->js cond #:names names* #:type type #:indent (format "~a\t" indent))) + (printf "~awhile (~a) {\n" indent test-var) + (define temp-vars (map gensym vars)) + (for ([temp-var temp-vars] [update updates]) + (printf "~a\tvar ~a = ~a;\n" indent (fix-name temp-var) + (expr->js update #:names names* #:type type #:indent (format "~a\t" indent)))) + (for ([var* vars*] [temp-var temp-vars]) + (printf "~a\t~a = ~a;\n" indent (fix-name var*) (fix-name temp-var))) + (printf "~a\t~a = ~a;" indent test-var + (expr->js cond #:names names* #:type type #:indent (format "~a\t" indent))) + (printf "~a}\n" indent) + (expr->js retexpr #:names names* #:type type #:indent indent)] + [(list (? operator? operator) args ...) + (define args_c + (map (λ (arg) (expr->js arg #:names names #:type type #:indent indent)) args)) + (application->js type operator args_c)] + [(? constant?) + (format "(~a)" (constant->js expr))] + [(? symbol?) + (fix-name (dict-ref names expr expr))] + [(? number?) + (format "~a" (real->double-flonum expr) )])) + +(define (compile-program prog #:name name) + (match-define (list 'FPCore (list args ...) props ... body) prog) + (define-values (_ properties) (parse-properties props)) + (define type (dict-ref properties ':precision 'binary64)) + + (define arg-strings + (for/list ([var args]) + (format "~a" (fix-name (if (list? var) (car var) var))))) + (define func-body + (with-output-to-string + (λ () + (parameterize ([*names* (apply mutable-set args)]) + (printf "\treturn ~a;\n" (expr->js body #:type type)))))) + (format "function ~a(~a) {\n~a}\n" + (fix-name name) + (string-join arg-strings ", ") + func-body)) + +(module+ main + (require racket/cmdline) + + (command-line + #:program "compile.rkt" + #:args (pkg) + (port-count-lines! (current-input-port)) + (printf "// Code generated by racket core2js.rkt DO NOT EDIT.\n\n") + (printf "var math = require('mathjs')\n\n") + (for ([expr (in-port (curry read-fpcore "stdin"))] [n (in-naturals)]) + (printf "~a\n" (compile-program expr #:name (format "Ex~a" n)))))) diff --git a/src/reports/fpcore-common.rkt b/src/reports/fpcore-common.rkt new file mode 100644 index 000000000..fd20da599 --- /dev/null +++ b/src/reports/fpcore-common.rkt @@ -0,0 +1,57 @@ +#lang racket + +(provide parse-properties unparse-properties constants operators constant? operator? define-by-match dictof property? property) + +(define (property? symb) + (and (symbol? symb) (string-prefix? (symbol->string symb) ":"))) + +(define (parse-properties lines) + (let loop ([lines lines] [props '()]) + (match lines + [(list (? property? prop) value rest ...) + (loop rest (cons (cons prop value) props))] + [(list _ ...) + (values lines (reverse props))]))) + +(define/match (cons->list x) + [((cons a b)) (list a b)]) + +(define (unparse-properties properties) + (append-map cons->list properties)) + +(define operators + (append + '(+ - * / fabs fma exp exp2 expm1 log log10 log2 log1p pow sqrt + cbrt hypot sin cos tan asin acos atan atan2 sinh cosh tanh + asinh acosh atanh erf erfc tgamma lgamma ceil floor fmod + remainder fmax fmin fdim copysign trunc round nearbyint) + '(< > <= >= == != and or not isfinite isinf isnan isnormal signbit))) + +(define (operator? x) + (set-member? operators x)) + +(define constants + '(E LOG2E LOG10E LN2 LN10 + PI PI_2 PI_4 1_PI 2_PI 2_SQRTPI + SQRT2 SQRT1_2 MAXFLOAT HUGE_VAL + TRUE FALSE)) + +(define (constant? x) + (set-member? constants x)) + +(define-syntax-rule (define-by-match name patterns ...) + (define/contract name + contract? + (flat-named-contract + 'name + (λ (var) + (let name ([var var]) + (match var + [patterns true] ... + [_ false])))))) + +(define-syntax-rule (property name statment) + (void)) + +(define (dictof key/c value/c) + (or/c (hash/c key/c value/c) (listof (cons/c key/c value/c)))) diff --git a/src/reports/fpcore.rkt b/src/reports/fpcore.rkt new file mode 100644 index 000000000..c35f6b3c7 --- /dev/null +++ b/src/reports/fpcore.rkt @@ -0,0 +1,261 @@ +#lang racket + +(require "fpcore-common.rkt" math/flonum math/bigfloat math/special-functions math/base) +(provide + (struct-out evaluator) racket-double-evaluator racket-single-evaluator + fpcore? expr? context/c eval-expr* eval-expr racket-run-fpcore + read-fpcore) + +(struct evaluator (real constant function)) + +(define/contract (fpcore? thing) + contract? + (match thing + [`(FPCore (,(? symbol?) ...) ,props ... ,(? expr?)) + (define-values (rest props*) (parse-properties props)) + (null? rest)] + [_ false])) + +(define-by-match expr? + (? number?) + (? constant?) + (? symbol?) + (list (? operator?) (? expr?) ...) + `(if ,(? expr?) ,(? expr?) ,(? expr?)) + `(let ([,(? symbol?) ,(? expr?)] ...) ,(? expr?)) + `(while ,(? expr?) ([,(? symbol?) ,(? expr?) ,(? expr?)] ...) ,(? expr?))) + +(define type? (symbols 'boolean 'real)) + +(define/match (operator-type op args) + [((or '- 'fabs 'exp 'exp2 'expm1 'log 'log10 'log2 'log1p 'sqrt + 'cbrt 'sin 'cos 'tan 'asin 'acos 'atan 'sinh 'cosh 'tanh + 'asinh 'acosh 'atanh 'erf 'erfc 'tgamma 'lgamma 'ceil 'floor + 'trunc 'round 'nearbyint) + (list 'real)) + 'real] + [((or '+ '- '* '/ 'pow 'hypot 'atan2 'fmod 'remainder 'fmax 'fmin 'fdim 'copysign) + (list 'real 'real)) + 'real] + [('fma (list 'real 'real 'real)) 'real] + [((or '< '> '<= '>= '== '!=) (list 'real ...)) 'boolean] + [((or 'isfinite 'isinf 'isnan 'isnormal 'signbit) 'real) 'boolean] + [((or 'and 'or) (list 'boolean ...)) 'boolean] + [('not (list 'boolean)) 'boolean] + [(_ _) #f]) + +(define/contract (check-expr stx ctx) + (-> syntax? (dictof symbol? type?) (cons/c expr? type?)) + + (match (syntax-e stx) + [(? number? val) + (cons val 'real)] + [(? constant? val) + (cons val (match val [(or 'TRUE 'FALSE) 'boolean] [_ 'real]))] + [(? symbol? var) + (unless (dict-has-key? ctx var) + (raise-syntax-error #f "Undefined variable" stx)) + (cons var (dict-ref ctx var))] + [(list (app syntax-e 'if) test ift iff) + (define test* (check-expr test ctx)) + (unless (equal? (cdr test*) 'boolean) + (raise-syntax-error #f "Conditional test must return a boolean" stx test)) + (define ift* (check-expr ift ctx)) + (define iff* (check-expr iff ctx)) + (unless (equal? (cdr ift*) (cdr iff*)) + (raise-syntax-error #f "Conditional branches must have same type" stx)) + (cons `(if ,(car test*) ,(car ift*) ,(car iff*)) (cdr ift*))] + [(cons (app syntax-e 'if) _) + (raise-syntax-error #f "Invalid conditional statement" stx)] + [(list (app syntax-e 'let) (app syntax-e (list (app syntax-e (list vars vals)) ...)) body) + (define vars* + (for/list ([var vars]) + (unless (symbol? (syntax-e var)) + (raise-syntax-error #f "Only variables may be bound by let binding" stx var)) + (syntax-e var))) + (define vals* (map (curryr check-expr ctx) vals)) + (define ctx* (apply dict-set* ctx (append-map list vars* (map cdr vals*)))) + (define body* (check-expr body ctx*)) + (cons `(let (,@(map list vars* (map car vals*))) ,(car body*)) (cdr body*))] + [(cons (app syntax-e 'let) _) + (raise-syntax-error #f "Invalid let bindings" stx)] + [(list (app syntax-e 'while) test (app syntax-e (list (app syntax-e (list vars inits updates)) ...)) body) + (define vars* + (for/list ([var vars]) + (unless (symbol? (syntax-e var)) + (raise-syntax-error #f "Only variables may be bound by while loop" stx var)) + (syntax-e var))) + (define inits* (map (curryr check-expr ctx) inits)) + (define ctx* (apply dict-set* ctx (append-map list vars* (map cdr inits*)))) + (define test* (check-expr test ctx*)) + (unless (equal? (cdr test*) 'boolean) + (raise-syntax-error #f "While loop conditions must return a boolean" test)) + (define updates* (map (curryr check-expr ctx*) updates)) + (for ([var vars] [init inits*] [update updates*]) + (unless (equal? (cdr init) (cdr update)) + (raise-syntax-error #f "Initialization and update must have the same type in while loop" stx var))) + (define body* (check-expr body ctx*)) + (cons `(while ,(car test*) (,@(map list vars* (map car inits*) (map car updates*))) ,(car body*)) (cdr body*))] + [(cons (app syntax-e 'while) _) + (raise-syntax-error #f "Invalid while loop" stx)] + [(list op args ...) + (unless (set-member? operators (syntax-e op)) + (raise-syntax-error #f "Unknown operator" op)) + (define children (map (curryr check-expr ctx) args)) + (define rtype (operator-type (syntax-e op) (map cdr children))) + (unless rtype + (raise-syntax-error #f (format "Invalid types for operator ~a" op) stx)) + (cons (list* (syntax-e op) (map car children)) rtype)])) + +(define/contract (check-fpcore stx) + (-> syntax? fpcore?) + (match (syntax-e stx) + [(list (app syntax-e 'FPCore) (app syntax-e (list vars ...)) properties ... body) + (define ctx + (for/hash ([var vars]) + (unless (symbol? (syntax-e var)) + (raise-syntax-error #f "FPCore parameters must be variables" stx var)) + (values (syntax-e var) 'real))) + + (define properties* + (let loop ([properties properties]) + (match properties + [(list) (list)] + [(list prop) (raise-syntax-error #f "Property with no value" prop)] + [(list (app syntax-e (? property? prop)) value rest ...) + (cons (cons prop value) (loop rest))] + [(list prop _ ...) (raise-syntax-error #f "Invalid property" prop)]))) + + (when (dict-has-key? properties* ':pre) + (define pre (dict-ref properties* ':pre)) + (define pre* (check-expr pre ctx)) + (unless (equal? (cdr pre*) 'boolean) + (raise-syntax-error #f "FPCore precondition must return a boolean" pre))) + + (define body* (check-expr body ctx)) + (unless (equal? (cdr body*) 'real) + (raise-syntax-error #f "FPCore benchmark must return a real number" body)) + + `(FPCore (,@(dict-keys ctx)) + ,@(apply append + (for/list ([(prop val) (in-dict properties*)]) + (list prop (syntax->datum val)))) + ,(car body*))])) + +(define/contract (read-fpcore name p) + (-> any/c input-port? (or/c fpcore? eof-object?)) + (parameterize ([read-decimal-as-inexact #f]) + (define stx (read-syntax name p)) + (if (eof-object? stx) stx (check-fpcore stx)))) + +(define/contract context/c contract? (dictof symbol? any/c)) + +(define/contract ((eval-expr* evaltor rec) expr ctx) + (-> evaluator? (-> expr? context/c any/c) (-> expr? context/c any/c)) + (match expr + [(? number?) ((evaluator-real evaltor) expr)] + [(? constant?) ((evaluator-constant evaltor) expr)] + [(? symbol?) (dict-ref ctx expr)] + [`(if ,test ,ift ,iff) + (if (rec test ctx) (rec ift ctx) (rec iff ctx))] + [`(let ([,vars ,vals] ...) ,body) + (define vals* (for/list ([val vals]) (rec val ctx))) + (define ctx* (apply dict-set* ctx (append-map list vars vals*))) + (rec body ctx*)] + [`(while ,test ([,vars ,inits ,updates] ...) ,res) + (define vals* (for/list ([init inits]) (rec init ctx))) + (define ctx* (apply dict-set* ctx (append-map list vars vals*))) + (if (rec test ctx*) + (let ([inits* (for/list ([update updates]) (rec update ctx*))]) + (rec + `(while ,test + ,(for/list ([var vars] [init inits] [update updates]) + (list var (rec update ctx*) update)) + ,res) + ctx)) + (rec res ctx*))] + [(list (? operator? op) args ...) + (apply ((evaluator-function evaltor) op) + (map (curryr rec ctx) args))])) + +(define/contract ((eval-expr evaltor) expr ctx) + (-> evaluator? (-> expr? context/c any/c)) + (let eval ([expr expr] [ctx ctx]) + ((eval-expr* evaltor eval) expr ctx))) + +(define-syntax-rule (table-fn [var val] ...) + (match-lambda + [`var val] ... + [unsupported-value + (error 'eval-expr "Unimplemented operation ~a" + unsupported-value)])) + +(define (my!= #:cmp [cmp =] . args) (not (check-duplicates args cmp))) +(define (my= #:cmp [cmp =] . args) + (match args ['() true] [(cons hd tl) (andmap (curry cmp hd) tl)])) + +(define/contract racket-double-evaluator evaluator? + (evaluator + real->double-flonum + (table-fn + [E 2.71828182845904523540] + [LOG2E 1.44269504088896340740] + [LOG10E 0.43429448190325182765] + [LN2 0.69314718055994530942] + [LN10 2.30258509299404568402] + [PI 3.14159265358979323846] + [PI_2 1.57079632679489661923] + [PI_4 0.78539816339744830962] + [1_PI 0.31830988618379067154] + [2_PI 0.63661977236758134308] + [2_SQRTPI 1.12837916709551257390] + [SQRT2 1.41421356237309504880] + [SQRT1_2 0.70710678118654752440] + [NAN +nan.0] + [INFINITY +inf.0] + [TRUE #t] [FALSE #f]) + (table-fn + [+ +] [- -] [* *] [/ /] [fabs abs] + [exp exp] [exp2 (λ (x) (expt 2.0 x))] + [log log] [log10 (λ (x) (/ (log x) (log 10.0)))] + [log2 (λ (x) (/ (log x) (log 2.0)))] + [pow expt] [sqrt sqrt] + [hypot flhypot] [sin sin] [cos cos] [tan tan] [asin asin] + [acos acos] [atan atan] [atan2 atan] [sinh sinh] [cosh cosh] + [tanh tanh] [asinh asinh] [acosh acosh] [atanh atanh] + [erf erf] [erfc erfc] [tgamma gamma] [lgamma log-gamma] + [ceil ceiling] [floor floor] [trunc truncate] [round round] + [fmax max] [fmin min] + [fdim (λ (x y) (abs (- x y)))] + [< <] [> >] [<= <=] [>= >=] [== my=] [!= my!=] + [and (λ (x y) (and x y))] [or (λ (x y) (or x y))] [not not] + [isnan nan?] [isinf infinite?] + [isfinite (λ (x) (not (or (nan? x) (infinite? x))))] + ; TODO: Currently unsupported + ;[fma '?] [expm1 '?] [log1p '?] [isnormal '?] [signbit '?] + ;[fmod '?] [remainder '?] [copysign '?] [nearbyint '?] + ))) + +(define/contract racket-single-evaluator evaluator? + (struct-copy evaluator racket-double-evaluator + [real real->single-flonum] + [constant (λ (x) (real->single-flonum ((evaluator-constant racket-double-evaluator) x)))])) + +(define/contract (racket-run-fpcore prog vals) + (-> fpcore? (listof real?) real?) + (match-define `(FPCore (,vars ...) ,props* ... ,body) prog) + (define-values (_ props) (parse-properties props*)) + (define evaltor + (match (dict-ref props ':precision 'binary64) + ['binary64 racket-double-evaluator] + ['binary32 racket-single-evaluator])) + ((eval-expr evaltor) body (map cons vars (map (evaluator-real evaltor) vals)))) + +(module+ main + (command-line + #:program "fpcore.rkt" + #:args args + (port-count-lines! (current-input-port)) + (let ([vals (map (compose real->double-flonum string->number) args)]) + (for ([prog (in-port (curry read-fpcore "stdin"))]) + (printf "~a\n" (racket-run-fpcore prog vals)))))) diff --git a/src/reports/make-graph.rkt b/src/reports/make-graph.rkt index 36ee244e4..3cb4f0d13 100644 --- a/src/reports/make-graph.rkt +++ b/src/reports/make-graph.rkt @@ -11,10 +11,14 @@ (require "../plot.rkt") (require "../sandbox.rkt") (require "../formats/tex.rkt") +(require "core2js.rkt") +(require (only-in xml write-xexpr xexpr?)) -(provide make-graph make-traceback make-timeout make-axis-plot make-points-plot make-plots) +(provide make-graph make-traceback make-timeout make-axis-plot make-points-plot + make-plots output-interactive-js make-interactive-js get-interactive-js) -(define (regime-var alt) +(define/contract (regime-var alt) + (-> alternative? (or/c expr? #f)) (let loop ([alt alt]) (match alt [(alt-event _ `(regimes ,splitpoints) prevs) @@ -23,7 +27,8 @@ [(alt-event _ _ (list prev _ ...)) (loop prev)] [(alt-delta _ _ prev) (loop prev)]))) -(define (regime-splitpoints alt) +(define/contract (regime-splitpoints alt) + (-> alternative? (listof number?)) (let loop ([alt alt]) (match alt [(alt-event _ `(regimes ,splitpoints) prevs) @@ -32,41 +37,129 @@ [(alt-event _ _ (list prev _ ...)) (loop prev)] [(alt-delta _ _ prev) (loop prev)]))) -(define (render-process-info time timeline profile? test #:bug? [bug? #t]) - (printf "
      \n") - (printf "

      Runtime

      \n") - (printf "

      ") - (printf "Time bar (total: ~a)\n" (format-time time)) - (printf "Debug log") - (when profile? - (printf "Profile")) - (printf "

      ") - (output-timeline timeline) - (when bug? - (printf "

      Please include this information when filing a bug report:

      \n")) - (printf "
      ")
      -  (printf "herbie shell --seed '~a'" (get-seed))
      -  (for ([rec (changed-flags)])
      -    (match rec
      -      [(list 'enabled class flag) (printf " +o ~a:~a" class flag)]
      -      [(list 'disabled class flag) (printf " -o ~a:~a" class flag)]))
      -  (printf "\n")
      -
      -  (printf "(FPCore ~a\n" (test-vars test))
      -  (printf "  :name ~s\n" (test-name test))
      -  (unless (equal? (test-precondition test) 'TRUE)
      -    (printf "  :pre ~a\n" (test-precondition test)))
      -  (unless (andmap (curry equal? 'default) (test-sampling-expr test))
      -    (printf "  :herbie-samplers ~a\n"
      -            (for/list ([var (test-vars test)] [samp (test-sampling-expr test)]
      -                       #:unless (equal? samp 'default))
      -              (list var samp))))
      -  (unless (equal? (test-expected test) #t)
      -    (printf "  :herbie-expected ~a\n" (test-expected test)))
      -  (when (test-output test) (printf "\n  :target\n  ~a\n\n" (test-output test)))
      -  (printf "  ~a)" (test-input test))
      -  (printf "
      \n") - (printf "
      \n")) +(define/contract (render-command-line) + (-> string?) + (format + "herbie shell --seed ~a ~a" + (if (vector? (get-seed)) (format "'~a'" (get-seed)) (get-seed)) + (string-join + (for/list ([rec (changed-flags)]) + (match rec + [(list 'enabled class flag) (format "+o ~a:~a" class flag)] + [(list 'disabled class flag) (format "-o ~a:~a" class flag)])) + " "))) + +(define/contract (render-fpcore test) + (-> test? string?) + (string-join + (filter + identity + (list + (format "(FPCore ~a" (test-vars test)) + (format " :name ~s" (test-name test)) + (if (equal? (test-precondition test) 'TRUE) + #f + (format " :pre ~a" (test-precondition test))) + (if (equal? (test-expected test) #t) + #f + (format " :herbie-expected ~a" (test-expected test))) + (if (test-output test) + (format "\n :herbie-target\n ~a\n" (test-output test)) ; Extra newlines for clarity + #f) + (format " ~a)" (test-input test)))) + "\n")) + +(define timeline? any/c) + +(define/contract (render-timeline timeline) + (-> timeline? xexpr?) + `(div ((class "timeline")) + ,@(for/list ([curr timeline] [next (cdr timeline)]) + `(div + ((class ,(format "timeline-phase ~a" (dict-ref curr 'type))) + (data-timespan ,(~a (- (dict-ref next 'time) (dict-ref curr 'time)))) + ,@(for/list ([(type value) (in-dict curr)] #:when (not (member type '(time)))) + `(,(string->symbol (format "data-~a" type)) ,(~a value)))))))) + + +(define/contract (render-process-info time timeline profile? test #:bug? [bug? #f]) + (->* (number? timeline? boolean? test?) (#:bug? boolean?) xexpr?) + `(section ((id "process-info")) + (h1 "Runtime") + (p ((class "header")) + "Time bar (total: " (span ((class "number")) ,(format-time time)) ")" + (a ((class "attachment") (href "debug.txt")) "Debug log") + ,(if profile? + `(a ((class "attachment") (href "profile.txt")) "Profile") + "") + ,(render-timeline timeline) + ,(if bug? + `(p "Please include this information when filing a " + (a ((href "https://github.com/uwplse/herbie/issues")) "bug report") ":") + "") + (pre ((class "shell")) + (code + ,(render-command-line) "\n" + ,(render-fpcore test) "\n"))))) + +(define (alt2fpcore alt) + (match-define (list _ args expr) (alt-program alt)) + (list 'FPCore args ':name 'alt expr)) + +(define (get-interactive-js result) + (with-handlers ([exn:fail? + (λ (e) #f)]) + (define start-fpcore (alt2fpcore (test-result-start-alt result))) + (define end-fpcore (alt2fpcore (test-result-end-alt result))) + (define start-js (compile-program start-fpcore #:name "start")) + (define end-js (compile-program end-fpcore #:name "end")) + (string-append start-js end-js))) + +(define (make-interactive-js result rdir profile?) + (define js-text (get-interactive-js result)) + (if (string? js-text) + (display-to-file js-text + (build-path rdir "interactive.js") + #:exists 'replace) + #f)) + +(define (output-interactive-js result rdir profile?) + (define js-text (get-interactive-js result)) + (if (string? js-text) + (display js-text) + #f)) + +(define/contract (render-interactive start-prog point) + (-> alternative? (listof number?) xexpr?) + (define start-fpcore (alt2fpcore start-prog)) + `(section ([id "try-it"]) + (h1 "Try it out") + (div ([id "try-inputs-wrapper"]) + (form ([id "try-inputs"]) + (p ([class "header"]) "Your Program's Arguments") + (ol + ,@(for/list ([var-name (second start-fpcore)] [i (in-naturals)] [val point]) + `(li (label ([for ,(string-append "var-name-" (~a i))]) ,(~a var-name)) + (input ([type "text"] + [name ,(string-append "var-name-" (~a i))] + [class "input-submit"] + [oninput "submit_inputs();"] + [value ,(~a val)]))))))) + (div ([id "try-result"] [class "no-error"]) + (p ([class "header"]) "Results") + (table + (tbody + (tr + (td + (label ([for "try-original-output"]) "In")) + (td + (output ([id "try-original-output"])))) + (tr + (td + (label ([for "try-herbie-output"]) "Out")) + (td + (output ([id "try-herbie-output"])))))) + (div ([id "try-error"]) "Enter valid numbers for all inputs")))) (define (make-axis-plot result idx out) (define var (list-ref (test-vars (test-result-test result)) idx)) @@ -106,270 +199,232 @@ (open-file idx #:type 'g make-points-plot result idx 'g)) (open-file idx #:type 'b make-points-plot result idx 'b)))) -(define (make-graph result rdir profile?) - (match result - [(test-result test time bits start-alt end-alt points exacts - start-est-error end-est-error newpoints newexacts start-error end-error target-error timeline) - (printf "\n") - (printf "\n") - (printf "") - (printf "") - (printf "Results for ~a" (test-name test)) - (printf "") - (printf "" mathjax-url) - (printf "") - (printf "\n") - (printf "\n") - - - ; Big bold numbers - (printf "
      \n") - (printf "
      Average Error: ~a → ~a
      \n" - (format-bits (apply max (map ulps->bits start-error)) #:unit #f) - (format-bits (apply max (map ulps->bits end-error)) #:unit #f) - (format-bits (errors-score start-error) #:unit #f) - (format-bits (errors-score end-error) #:unit #f)) - (printf "
      Time: ~a
      \n" (format-time time)) - (printf "
      Precision: ~a
      \n" (format-bits (*bit-width*) #:unit #f)) - (printf "
      Internal precision: ~a
      \n" (format-bits bits #:unit #f)) - (printf "
      \n") - - - (printf "
      \n") - (printf "
      \\[~a\\]
      \n" - (texify-prog (alt-program start-alt))) - (printf "
      ") - (printf "
      \\[~a\\]
      \n" - (texify-prog (alt-program end-alt))) - (printf "
      \n") - - - (printf "
      \n") - (printf "

      Error

      \n") - (printf "
      \n") - (for ([var (test-vars test)] [idx (in-naturals)]) - (when (> (length (remove-duplicates (map (curryr list-ref idx) newpoints))) 1) - (define split-var? (equal? var (regime-var end-alt))) - - (define title "The X axis uses an exponential scale") - (printf "
      " idx (if split-var? "class='default'" "")) - (printf "" idx title) - (printf "" idx title) - (when target-error - (printf "" idx title)) - (printf "" idx title) - (printf "

      Bits error versus ~a

      " var) - (printf "
      \n"))) - (printf "
      \n") - (printf "
      \n") - - (when (test-output test) - (printf "
      \n") - (printf "

      Target

      ") - (printf "\n") - (printf "\n" - (format-bits (errors-score start-error))) - (printf "\n" - (format-bits (errors-score target-error))) - (printf "\n" - (format-bits (errors-score end-error))) - (printf "
      Original~a
      Comparison~a
      Herbie~a
      \\[ ~a \\]
      \n" - (texify-prog `(λ ,(test-vars test) ,(test-output test)))) - (printf "
      \n")) - - - (printf "
      \n") - (printf "

      Derivation

      \n") - (printf "
        \n") - (parameterize ([*pcontext* (mk-pcontext newpoints newexacts)] - [*start-prog* (alt-program start-alt)]) - (output-history end-alt)) - (printf "
      \n") - (printf "
      \n") - - - (render-process-info time timeline profile? test) - - - (printf "\n") - (printf "\n")])) +(define (make-graph result rdir profile? valid-js-prog) + (match-define + (test-result test time bits start-alt end-alt + points exacts start-est-error end-est-error + newpoints newexacts start-error end-error target-error timeline) + result) + + (printf "\n") + (write-xexpr + `(html + (head + (meta ([charset "utf-8"])) + (title "Result for " ,(~a (test-name test))) + (link ([rel "stylesheet"] [type "text/css"] [href "../graph.css"])) + (script ([src ,mathjax-url])) + (script ([src "../report.js"])) + (script ([src "interactive.js"])) + (script ([src "https://unpkg.com/mathjs@4.4.2/dist/math.min.js"]))) + (body ([onload "graph()"]) + + (section ([id "large"]) + (div "Average Error: " + (span ([class "number"] + [title ,(format "Maximum error: ~a → ~a" + (format-bits (apply max (map ulps->bits start-error)) #:unit #f) + (format-bits (apply max (map ulps->bits end-error)) #:unit #f))]) + ,(format-bits (errors-score start-error) #:unit #f) + " → " + ,(format-bits (errors-score end-error) #:unit #f))) + (div "Time: " (span ([class "number"]) ,(format-time time))) + (div "Precision: " (span ([class "number"]) ,(format-bits (*bit-width*) #:unit #f))) + (div "Internal Precision: " (span ([class "number"]) ,(format-bits bits #:unit #f)))) + + (section ([id "program"]) + (div ([class "program"]) "\\[" ,(texify-prog (alt-program start-alt)) "\\]") + (div ([class "arrow"]) "↓") + (div ([class "program"]) "\\[" ,(texify-prog (alt-program end-alt)) "\\]")) + + (section ([id "graphs"]) + (h1 "Error") + (div + ,@(for/list ([var (test-vars test)] [idx (in-naturals)]) + (cond + [(> (length (remove-duplicates (map (curryr list-ref idx) newpoints))) 1) + (define split-var? (equal? var (regime-var end-alt))) + (define title "The X axis uses an exponential scale") + `(figure ([id ,(format "fig-~a" idx)] [class ,(if split-var? "default" "")]) + (img ([width "800"] [height "300"] [title ,title] + [src ,(format "plot-~a.png" idx)])) + (img ([width "800"] [height "300"] [title ,title] [data-name "Input"] + [src ,(format "plot-~ar.png" idx)])) + ,(if target-error + `(img ([width "800"] [height "300"] [title ,title] [data-name "Target"] + [src ,(format "plot-~ag.png" idx)])) + "") + (img ([width "800"] [height "300"] [title ,title] [data-name "Result"] + [src ,(format "plot-~ab.png" idx)])) + (figcaption (p "Bits error versus " (var ,(~a var)))))] + [else ""])))) + + ,(if valid-js-prog + (render-interactive start-alt (car points)) + `(p ([display "none"]))) + + ,(if (test-output test) + `(section ([id "comparison"]) + (h1 "Target") + (table + (tr (th "Original") (td ,(format-bits (errors-score start-error)))) + (tr (th "Target") (td ,(format-bits (errors-score target-error)))) + (tr (th "Herbie") (td ,(format-bits (errors-score end-error))))) + (div "\\[" ,(texify-prog `(λ ,(test-vars test) ,(test-output test))) "\\]")) + "") + + (section ([id "history"]) + (h1 "Derivation") + (ol ([class "history"]) + ,@(parameterize ([*pcontext* (mk-pcontext newpoints newexacts)] + [*start-prog* (alt-program start-alt)]) + (render-history end-alt)))) + + ,(render-process-info time timeline profile? test))))) (define (make-traceback result rdir profile?) - (match result - [(test-failure test bits exn time timeline) - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "Exception for ~a" (test-name test)) - (printf "") - (printf "") - (printf "\n") - - (printf "

      Error in ~a

      \n" (format-time time)) - - (cond - [(exn:fail:user:herbie? exn) - (printf "
      \n") - (printf "

      ~a (more)

      \n" - (exn-message exn) *herbie-version* (exn:fail:user:herbie-url exn)) - - (when (exn:fail:user:herbie:syntax? exn) - (printf "\n") - (printf "\n") - (printf "\n" (html-escape-unsafe (exn-message exn))) - (printf "\n") - (for ([(stx msg) (in-dict (exn:fail:user:herbie:syntax-locations exn))]) - (printf "\n" - msg (syntax-source stx) (or (syntax-line stx) "") - (or (syntax-column stx) (syntax-position stx)))) - (printf "
      ~aLC
      ~a~a~a~a
      \n")) - - (printf "
      ")] - [else - (render-process-info time timeline profile? test #:bug? #t) - - (printf "
      \n") - (printf "

      Backtrace

      \n") - (printf "\n") - (printf "\n") - (printf "\n" (html-escape-unsafe (exn-message exn))) - (printf "\n") - (for ([tb (continuation-mark-set->context (exn-continuation-marks exn))]) - (match (cdr tb) - [(srcloc file line col _ _) - (printf "\n" - (procedure-name->string (car tb)) file line col)] - [#f - (printf "" - (procedure-name->string (car tb)))])) - (printf "
      ~aLC
      ~a~a~a~a
      ~aunknown
      \n")]) - (printf "
      ") - - (printf "\n") - (printf "\n")])) + (match-define (test-failure test bits exn time timeline) result) + (printf "\n") + (write-xexpr + `(html + (head + (meta ((charset "utf-8"))) + (title "Exception for " ,(~a (test-name test))) + (link ((rel "stylesheet") (type "text/css") (href "../graph.css")))) + (body + (h1 "Error in " ,(format-time time)) + ,@(cond + [(exn:fail:user:herbie? exn) + `((section ([id "user-error"]) + (h2 ,(~a (exn-message exn)) (a ([href ,(herbie-error-url exn)]) " (more)")) + ,(if (exn:fail:user:herbie:syntax? exn) + `(table + (thead + (th ([colspan "2"]) ,(exn-message exn)) (th "L") (th "C")) + (tbody + ,@(for/list ([(stx msg) (in-dict (exn:fail:user:herbie:syntax-locations exn))]) + `(tr + (td ([class "procedure"]) ,(~a msg)) + (td ,(~a (syntax-source stx))) + (td ,(or (~a (syntax-line stx) ""))) + (td ,(or (~a (syntax-column stx)) (~a (syntax-position stx)))))))) + "")))] + [else + `(,(render-process-info time timeline profile? test #:bug? #t) + (section ([id "backtrace"]) + (h1 "Backtrace") + (table + (thead + (th ([colspan "2"]) ,(exn-message exn)) (th "L") (th "C")) + (tbody + ,@(for/list ([tb (continuation-mark-set->context (exn-continuation-marks exn))]) + (match (cdr tb) + [(srcloc file line col _ _) + `(tr + (td ([class "procedure"]) ,(procedure-name->string (car tb))) + (td ,(~a file)) + (td ,(~a line)) + (td ,(~a col)))] + [#f + `(tr + (td ([class "procedure"]) ,(procedure-name->string (car tb))) + (td ([colspan "3"]) "unknown"))]))))))]))))) (define (make-timeout result rdir profile?) - (match result - [(test-timeout test bits time timeline) - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "Timeout for ~a" (test-name test)) - (printf "") - (printf "") - (printf "\n") - - (printf "

      Timeout in ~a

      \n" (format-time time)) - (printf "

      Use the --timeout flag to change the timeout.

      \n") - - (render-process-info time timeline profile? test) - - (printf "\n") - (printf "\n")])) + (match-define (test-timeout test bits time timeline) result) + (printf "\n") + (write-xexpr + `(html + (head + (meta ((charset "utf-8"))) + (title ,(format "Timeout for ~a" (test-name test))) + (link ([rel "stylesheet"] [type "text/css"] [href "../graph.css"]))) + (body + (h1 "Timeout in " ,(format-time time)) + (p "Use the " (code "--timeout") " flag to change the timeout.") + ,(render-process-info time timeline profile? test))))) (struct interval (alt-idx start-point end-point expr)) -(define (output-history altn) +(define (render-history altn) + (-> alternative? (listof xexpr?)) + (define err (format-bits (errors-score (alt-errors altn)))) (match altn [(alt-event prog 'start _) - (printf "
    • Initial program ~a

      \\[~a\\]
    • \n" - err (texify-prog prog))] - + (list + `(li (p "Initial program " (span ([class "error"]) ,err)) + (div "\\[" ,(texify-prog prog) "\\]")))] [(alt-event prog `(start ,strategy) `(,prev)) - (output-history prev) - (printf "
    • Using strategy ~a
    • \n" - strategy)] + `(,@(render-history prev) + (li ([class "event"]) "Using strategy " (code ,(~a strategy))))] [(alt-event _ `(regimes ,splitpoints) prevs) - (let* ([start-sps (cons (sp -1 -1 -inf.0) (take splitpoints (sub1 (length splitpoints))))] + (let* ([start-sps (cons (sp -1 -1 #f) (take splitpoints (sub1 (length splitpoints))))] [vars (program-variables (alt-program altn))] [intervals (for/list ([start-sp start-sps] [end-sp splitpoints]) (interval (sp-cidx end-sp) (sp-point start-sp) (sp-point end-sp) (sp-bexpr end-sp)))] [preds (splitpoints->point-preds splitpoints (length prevs))] [interval->string + (λ (ival) (string-join (list - (if (ordinary-float? (interval-start-point ival)) - (format "~a < " (interval-start-point ival)) + (if (interval-start-point ival) + (format "~a < " (interval-start-point ival)) "") (~a (interval-expr ival)) - (if (ordinary-float? (interval-end-point ival)) - (format " < ~a" (interval-end-point ival)) - ""))))]) - (printf "
    • Split input into ~a regimes.
    • \n" (length prevs)) - (printf "
    • \n") - (for ([entry prevs] [entry-idx (range (length prevs))] [pred preds]) - (let* ([entry-ivals - (filter (λ (intrvl) (= (interval-alt-idx intrvl) entry-idx)) intervals)] - [condition - (string-join (map interval->string entry-ivals) " or ")]) - (define-values (ivalpoints ivalexacts) - (for/lists (pts exs) ([(pt ex) (in-pcontext (*pcontext*))] #:when (pred pt)) - (values pt ex))) - (printf "

      if ~a

      \n" condition) - (printf "
        \n") - ;; TODO: The (if) here just corrects for the possibility - ;; that we might have sampled new points that include no - ;; points in a given regime. Instead it would be best to - ;; continue sampling until we actually have many points in - ;; each regime. That would require breaking some - ;; abstraction boundaries right now so we haven't dont it - ;; yet. - (define new-pcontext - (if (null? ivalpoints) (*pcontext*) (mk-pcontext ivalpoints ivalexacts))) - (parameterize ([*pcontext* new-pcontext]) - (output-history entry)) - (printf "
      \n"))) - (printf "
    • \n") - (printf "
    • Recombined ~a regimes into one program.
    • \n" - (length prevs)))] + (if (equal? (interval-end-point ival) +nan.0) + "" + (format " < ~a" (interval-end-point ival))))))]) + `((li ([class "event"]) "Split input into " ,(~a (length prevs)) " regimes") + (li + ,@(apply + append + (for/list ([entry prevs] [entry-idx (range (length prevs))] [pred preds]) + (let* ([entry-ivals + (filter (λ (intrvl) (= (interval-alt-idx intrvl) entry-idx)) intervals)] + [condition + (string-join (map interval->string entry-ivals) " or ")]) + (define-values (ivalpoints ivalexacts) + (for/lists (pts exs) ([(pt ex) (in-pcontext (*pcontext*))] #:when (pred pt)) + (values pt ex))) + + ;; TODO: The (if) here just corrects for the possibility + ;; that we might have sampled new points that include no + ;; points in a given regime. Instead it would be best to + ;; continue sampling until we actually have many points in + ;; each regime. That would require breaking some + ;; abstraction boundaries right now so we haven't done it + ;; yet. + (define new-pcontext + (if (null? ivalpoints) (*pcontext*) (mk-pcontext ivalpoints ivalexacts))) + + `((h2 (code "if " (span ([class "condition"]) ,condition))) + (ol ,@(parameterize ([*pcontext* new-pcontext]) (render-history entry)))))))) + (li ([class "event"]) "Recombined " ,(~a (length prevs)) " regimes into one program.")))] [(alt-event prog `(taylor ,pt ,loc) `(,prev)) - (output-history prev) - (printf "
    • Taylor expanded around ~a ~a

      \\[\\leadsto ~a\\]
    • " - pt err (texify-prog prog #:loc loc #:color "blue"))] - - [(alt-event prog 'periodicity `(,base ,subs ...)) - (output-history base) - (for ([sub subs]) - (printf "
    • Optimizing periodic subexpression
    • \n") - (output-history sub)) - (printf "
    • Combined periodic subexpressions
    • \n")] + `(,@(render-history prev) + (li (p "Taylor expanded around " ,(~a pt) " " (span ([class "error"]) ,err)) + (div "\\[\\leadsto " ,(texify-prog prog #:loc loc #:color "blue") "\\]")))] [(alt-event prog 'removed-pows `(,alt)) - (output-history alt) - (printf "
    • Removed slow pow expressions
    • \n")] + `(,@(render-history alt) + (li ([class "event"]) "Removed slow " (code "pow") " expressions."))] [(alt-event prog 'final-simplify `(,alt)) - (output-history alt) - (printf "
    • Applied final simplification
    • \n")] + `(,@(render-history alt) + (li ([class "event"]) "Applied final simplification."))] [(alt-delta prog cng prev) - (output-history prev) - (printf "
    • Applied ~a ~a

      " - (rule-name (change-rule cng)) err) - (printf "
      \\[\\leadsto ~a\\]
    • \n" - (texify-prog prog #:loc (change-location cng) #:color "blue"))])) - -(define (output-timeline timeline) - (printf "
      ") - (for ([curr timeline] [next (cdr timeline)]) - (printf "
      ")) - (printf "
      \n")) - + `(,@(render-history prev) + (li (p "Applied " (span ([class "rule"]) ,(~a (rule-name (change-rule cng)))) + (span ([class "error"]) ,err)) + (div "\\[\\leadsto " ,(texify-prog prog #:loc (change-location cng) #:color "blue") "\\]")))])) (define (procedure-name->string name) (if name - (html-escape-unsafe (~a name)) + (~a name) "(unnamed)")) diff --git a/src/reports/make-report.rkt b/src/reports/make-report.rkt index b0cd3d552..74779dc85 100644 --- a/src/reports/make-report.rkt +++ b/src/reports/make-report.rkt @@ -1,6 +1,6 @@ #lang racket -(require racket/date) +(require racket/date (only-in xml write-xexpr)) (require "../common.rkt" "common.rkt") (require "../formats/datafile.rkt") @@ -8,14 +8,12 @@ (define (log-exceptions file info) (define (print-test t) - (printf "(lambda ~a\n #:name ~s\n ~a)\n\n" - (for/list ([v (table-row-vars t)] - [s (table-row-samplers t)]) - (list v s)) + (printf "(FPCore ~a\n :name ~s\n ~a)\n\n" + (table-row-vars t) (table-row-name t) (table-row-input t))) (match info - [(report-info date commit branch seed flags points iterations bit-width note tests) + [(report-info date commit branch hostname seed flags points iterations bit-width note tests) (write-file file (printf "; seed : ~a\n\n" seed) (printf "; flags :\n") @@ -26,6 +24,9 @@ (printf "\n") (for ([t tests]) (match (table-row-status t) + ["error" + (printf "; errored\n") + (print-test t)] ["crash" (printf "; crashed\n") (print-test t)] @@ -37,13 +38,24 @@ (define (web-resource name) (build-path web-resource-path name)) +(define (badge-label result) + (match (table-row-status result) + ["error" "ERR"] + ["crash" "!!!"] + ["timeout" "TIME"] + [_ (format-bits (- (table-row-start result) (table-row-result result)) #:sign #t)])) + (define (make-report-page file info) (match info - [(report-info date commit branch seed flags points iterations bit-width note tests) + [(report-info date commit branch hostname seed flags points iterations bit-width note tests) (define table-labels '("Test" "Start" "Result" "Target" "∞ ↔ ℝ" "Time")) + (define help-text + #hash(("Result" . "Color key:\nGreen: improved accuracy\nLight green: no initial error\nOrange: no accuracy change\nRed: accuracy worsened") + ("Target" . "Color key:\nDark green: better than target\nGreen: matched target\nOrange: improved but did not match target\nYellow: no accuracy change\n"))) + (define-values (dir _name _must-be-dir?) (split-path file)) (copy-file (web-resource "report.js") (build-path dir "report.js") #t) @@ -77,115 +89,100 @@ (for*/or ([test tests] [field (list table-row-inf- table-row-inf+)]) (and (field test) (> (field test) 0)))) + (define sorted-tests + (sort (map cons tests (range (length tests))) > + #:key (λ (x) (or (table-row-start (car x)) 0)))) + + (define classes + (filter identity + (list (if any-has-target? #f 'no-target) + (if any-has-inf+/-? #f 'no-inf)))) + (write-file file ; HTML cruft (printf "\n") - (printf "\n") - (printf "Herbie test results\n") - (printf "") - (printf "") - - ; Scripts: the report script, MathJax, D3, and graph-drawing code - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - (printf "\n") - - ; Big bold numbers - (printf "
      \n") - (printf "
      Time: ~a
      \n" - (format-time total-time)) - (printf "
      Passed: ~a/~a
      \n" - total-passed total-available) - (when (not (= total-crashes 0)) - (printf "
      Crashes: ~a
      \n" - total-crashes)) - (printf "
      Tests: ~a
      \n" - (length tests)) - (printf "
      Bits: ~a/~a
      \n" - (round* (- total-start total-gained)) (round* total-start)) - (printf "
      \n") - - ; The graph - (printf "
      \n") - (printf "\n") - (printf "
      \n") - - ; Test badges - (printf "
        \n") - (define sorted-tests - (sort (map cons tests (range (length tests))) > - #:key (λ (x) (or (table-row-start (car x)) 0)))) - (for ([(result id) (in-dict sorted-tests)]) - (printf "
      • ~a
      • \n" - (table-row-status result) - (html-escape-unsafe (table-row-name result)) - (format-bits (table-row-start result)) - (format-bits (table-row-result result)) - id - (match (table-row-status result) - ["crash" "ERR"] - ["timeout" "TIME"] - [_ (format-bits (- (table-row-start result) (table-row-result result)) #:sign #t)]))) - (printf "
      \n") - (printf "
      \n") - - ; Run stats - (printf "\n") - (printf "\n" (date->string (current-date))) - (printf "\n" commit branch) - (printf "\n" (*num-points*)) - (printf "\n" (*num-iterations*)) - (printf "\n" seed) - (printf "") - (printf "
      Date:~a
      Commit:~a on ~a
      Points:~a
      Fuel:~a
      Seed:~a
      Flags:\n") - (printf "
      ") - (for* ([(class flags) (*flags*)] [flag flags]) - (printf "~a:~a" class flag)) - (printf "
      \n") - (printf "
      ") - (when (null? (changed-flags)) - (printf "default")) - (for ([rec (changed-flags)]) - (match rec - [(list 'enabled class flag) (printf "+o ~a:~a" class flag)] - [(list 'disabled class flag) (printf "-o ~a:~a" class flag)])) - (printf "
      \n") - (printf "
      \n") - - (define classes - (filter identity (list (if any-has-target? #f 'no-target) (if any-has-inf+/-? #f 'no-inf)))) - - ; Results table - (printf "\n" (string-join (map ~a classes) " ")) - (printf "") - (for ([label table-labels]) - (printf "" label)) - (printf "\n") - (printf "\n") - - (printf "") - (for ([result tests] [id (in-naturals)]) - (printf "" (table-row-status result)) - (printf "" (html-escape-unsafe (or (table-row-name result) ""))) - (printf "" (format-bits (table-row-start result))) - (printf "" (format-bits (table-row-result result))) - (printf "" (format-bits (table-row-target result))) - (printf "" - (let ([inf- (table-row-inf- result)]) - (if (and inf- (> inf- 0)) (format "+~a" inf-) "")) - (let ([inf+ (table-row-inf+ result)]) - (if (and inf+ (> inf+ 0)) (format "-~a" inf+) ""))) - (printf "" (format-time (table-row-time result))) - (if (table-row-link result) - (printf "" id (table-row-link result)) - (printf "")) - (printf "\n")) - (printf "\n") - (printf "
      ~a
      ~a~a~a~a~a~a~a»
      \n") - (printf "\n") - (printf "\n")) + (write-xexpr + `(html + (head + (title "Herbie results") + (meta ((charset "utf-8"))) + (link ((rel "stylesheet") (type "text/css") (href "report.css"))) + (script ((src "report.js"))) + (script ((src "http://d3js.org/d3.v3.min.js") (charset "utf-8"))) + (script ((type "text/javascript") (src "arrow-chart.js")))) + + (body ((onload "report()")) + (div ((id "large")) + (div "Time: " (span ((class "number")) ,(format-time total-time))) + (div "Passed: " (span ((class "number")) ,(~a total-passed) "/" ,(~a total-available))) + ,(if (> total-crashes 0) + `(div "Crashes: " (span ((class "number")) ,(~a total-crashes))) + "") + (div "Tests: " (span ((class "number")) ,(~a (length tests)))) + (div "Bits: " (span ((class "number")) + ,(~a (round* (- total-start total-gained))) + "/" + ,(~a (round* total-start))))) + + (figure + (svg ((id "graph") (width "400"))) + (script "window.addEventListener('load', function(){draw_results(d3.select('#graph'))})"))) + + (ul ((id "test-badges")) + ,@(for/list ([(result id) (in-dict sorted-tests)]) + `(li ((class ,(format "badge ~a" (table-row-status result))) + (title ,(format "~a (~a to ~a)" + (table-row-name result) + (format-bits (table-row-start result)) + (format-bits (table-row-result result)))) + (data-id ,(~a id))) + ,(badge-label result)))) + (hr ((style "clear:both;visibility:hidden"))) + + (table ((id "about")) + (tr (th "Date:") (td ,(date->string date))) + (tr (th "Commit:") (td ,commit " on " ,branch)) + (tr (th "Hostname:") (td ,hostname)) + (tr (th "Points:") (td ,(~a (*num-points*)))) + (tr (th "Fuel:") (td ,(~a (*num-iterations*)))) + (tr (th "Seed:") (td ,(~a seed))) + (tr (th "Flags:") + (td ((id "flag-list")) + (div ((id "all-flags")) + ,@(for*/list ([(class flags) (*flags*)] [flag flags]) + `(kbd ,(~a class) ":" ,(~a flag)))) + (div ((id "changed-flags")) + ,@(if (null? (changed-flags)) + '("default") + (for/list ([rec (changed-flags)]) + (match-define (list delta class flag) rec) + `(kbd ,(match delta ['enabled "+o"] ['disabled "-o"]) + " " ,(~a class) ":" ,(~a flag)))))))) + + (table ((id "results") (class ,(string-join (map ~a classes) " "))) + (thead + (tr ,@(for/list ([label table-labels]) + (if (dict-has-key? help-text label) + `(th ,label " " (span ([class "help-button"] [title ,(dict-ref help-text label)]) "?")) + `(th ,label))))) + (tbody + ,@(for/list ([result tests] [id (in-naturals)]) + `(tr ((class ,(~a (table-row-status result)))) + (td ,(or (table-row-name result) "")) + (td ,(format-bits (table-row-start result))) + (td ,(format-bits (table-row-result result))) + (td ,(format-bits (table-row-target result))) + (td ,(let ([inf- (table-row-inf- result)]) + (if (and inf- (> inf- 0)) (format "+~a" inf-) "")) + ,(let ([inf+ (table-row-inf+ result)]) + (if (and inf+ (> inf+ 0)) (format "-~a" inf+) ""))) + (td ,(format-time (table-row-time result))) + ,(if (table-row-link result) + `(td + (a ((id ,(format "link~a" id)) + (href ,(format "~a/graph.html" (table-row-link result)))) + "»")) + "")))))))) ; Delete old files (let* ([expected-dirs (map string->path (filter identity (map table-row-link tests)))] @@ -196,9 +193,9 @@ (delete-directory/files (build-path dir subdir)))))])) (define (make-compare-page out-file info1 info2) - (match-let ([(report-info date1 commit1 branch1 seed1 flags1 points1 iterations1 bit-width1 note1 tests1) + (match-let ([(report-info date1 commit1 branch1 hostname1 seed1 flags1 points1 iterations1 bit-width1 note1 tests1) info1] - [(report-info date2 commit2 branch2 seed2 flags2 points2 iterations2 bit-width2 note2 tests2) + [(report-info date2 commit2 branch2 hostname2 seed2 flags2 points2 iterations2 bit-width2 note2 tests2) info2]) (define table-labels '("Test" "Start" "Result" "Result" "Target" "∞ ↔ ℝ" "∞ ↔ ℝ" "Time" "Time")) @@ -209,19 +206,19 @@ (define total-time1 (apply + (map table-row-time tests1))) (define total-time2 (apply + (map table-row-time tests2))) - + (define (total-passed tests) (for/sum ([row tests]) (if (member (table-row-status row) '("gt-target" "eq-target" "imp-start")) 1 0))) (define total-passed1 (total-passed tests1)) (define total-passed2 (total-passed tests2)) - + (define (total-available tests) (for/sum ([row tests]) (if (not (equal? (table-row-status row) "ex-start")) 1 0))) (define total-available1 (total-passed tests1)) (define total-available2 (total-passed tests2)) - + (define (total-crashes tests) (for/sum ([row tests]) (if (equal? (table-row-status row) "crash") 1 0))) @@ -233,7 +230,7 @@ (or (table-row-result row) 0))) (define total-gained1 (total-gained tests1)) (define total-gained2 (total-gained tests2)) - + (define (total-start tests) (for/sum ([row tests]) (or (table-row-start row) 0))) @@ -250,164 +247,128 @@ (define (round* x) (inexact->exact (round x))) + (define (double-large-number title val1 val2) + `(div ,title ": " (span ([class "number"]) ,(~a val1)) + " vs " (span ([class "number"]) ,(~a val2)))) + + (write-file out-file - ; HTML cruft - (printf "\n") - (printf "\n") - (printf "Herbie test results\n") - (printf "") - (printf "") - - ; Scripts: D3. - (printf "\n") - (printf "\n") - (printf "\n") - - ; Big bold numbers - (printf "
      \n") - (printf "
      Time: ~a vs ~a
      \n" - (format-time total-time1) (format-time total-time2)) - (printf "
      Passed: ~a/~a vs ~a/~a
      \n" - total-passed1 total-available1 - total-passed2 total-available2) - (when (not (and (= total-crashes1 0) (= total-crashes2 0))) - (printf "
      Crashes: ~a vs ~a
      \n" - total-crashes1 total-crashes2)) - (printf "
      Tests: ~a vs ~a
      \n" - (length tests1) (length tests2)) - (printf "
      Bits: ~a/~a vs ~a/~a
      \n" - (round* (- total-start1 total-gained1)) (round* total-start1) - (round* (- total-start2 total-gained2)) (round* total-start2)) - (printf "
      \n") - - (define (badge-label result) - (match (table-row-status result) - ["crash" "ERR"] - ["timeout" "TIME"] - [_ (format-bits (- (table-row-start result) - (table-row-result result)) - #:sign #t)])) - - ; Test badges - (printf "
        \n") - (for ([name (remove-duplicates (map table-row-name (append tests1 tests2)))]) - (define result1 (findf (compose (curry equal? name) table-row-name) + ; HTML cruft + (printf "\n") + (write-xexpr + `(html + (head + (title "Herbie test results") + (meta ([charset "utf-8"])) + (link ([rel "stylesheet"] [type "text/css"] [href "compare.css"])) + (script ([src "http://d3js.org/d3.v3.min.js"] [charset "utf-8"]))) + (body + (div ([id "large"]) + ,(double-large-number "Time" (format-time total-time1) (format-time total-time2)) + ,(double-large-number "Passed" + (format "~a/~a" total-time1 total-available1) + (format "~a/~a" total-time2 total-available2)) + ,(if (and (= total-crashes1 0) (= total-crashes2 0)) + "" + (double-large-number "Crashes" total-crashes1 total-crashes2)) + ,(double-large-number "Tests" (length tests1) (length tests2)) + ,(double-large-number + "Bits" + (format "~a/~a" (round* (- total-start1 total-gained1)) (round* total-start1)) + (format "~a/~a" (round* (- total-start1 total-gained2)) (round* total-start2)))) + + (ul ([id "test-badges"]) + ,@(for/list ([name (remove-duplicates (map table-row-name (append tests1 tests2)))]) + (define result1 (findf (compose (curry equal? name) table-row-name) tests1)) + (define result2 (findf (compose (curry equal? name) table-row-name) tests2)) + `(li ([class "badge"] + [title ,(format "~a (~a to ~a) vs. (~a to ~a)" + (table-row-name result1) + (format-bits (table-row-start result1)) + (format-bits (table-row-result result1)) + (format-bits (table-row-start result2)) + (format-bits (table-row-result result2)))]) + (table + (tbody + (tr + (td ([class ,(~a (table-row-status result1))]) ,(badge-label result1)) + (td ([class ,(~a (table-row-status result2))]) ,(badge-label result1)))))))) + (hr ([style "clear:both;visibility:hidden"])) + + (table ([id "about"]) + (tr (th "Date:") + (td ([class "hinfo-cell"]) ,(date->string date1) (br) ,(date->string date2))) + (tr (th "Commit:") + (td ([class "hinfo-cell"]) ,(~a commit1) " on " ,(~a branch1) + (br) ,(~a commit2) " on " ,(~a branch2))) + (tr (th "Hostname:") + (td ([class "hinfo-cell"]) ,(~a hostname1) (br) ,(~a hostname2))) + (tr (th "Points:") + (td ([class "hinfo-cell"]) ,(~a points1) (br) ,(~a points2))) + (tr (th "Fuel:") + (td ([class "hinfo-cell"]) ,(~a iterations1) (br) ,(~a iterations2))) + (tr (th "Seed:") + (td ([class "hinfo-cell"]) ,(~a seed1) (br) ,(~a seed2))) + (tr (th "Flags:") + (td ([id "flag-list"] [class "hinfo-cell"]) + ,@(for*/list ([rec (hash->list flags1)] [fl (cdr rec)]) + `(kbd ,(format "~a:~a" (car rec) fl))) + (br) + ,@(for*/list ([rec (hash->list flags2)] [fl (cdr rec)]) + `(kbd ,(format "~a:~a" (car rec) fl)))))) + + (table ([id "results"]) + (thead ,@(for/list ([label table-labels]) `(th ,(~a label)))) + (tbody + ,@(for/list ([name (remove-duplicates (map table-row-name (append tests1 tests2)))] + [id (in-naturals)]) + (define result1 (findf (compose (curry equal? name) table-row-name) tests1)) - (define result2 (findf (compose (curry equal? name) table-row-name) + (define result2 (findf (compose (curry equal? name) table-row-name) tests2)) - (printf "
      • " - (html-escape-unsafe (table-row-name result1)) - (format-bits (table-row-start result1)) - (format-bits (table-row-result result1)) - (format-bits (table-row-start result2)) - (format-bits (table-row-result result2))) - (printf "
        ~a~a
        " - (table-row-status result1) - (badge-label result1) - (table-row-status result2) - (badge-label result2)) - (printf "
      • ")) - (printf "
      \n") - (printf "
      \n") - - ; Run stats - (printf "\n") - (printf "\n" - (date->string date1) (date->string date2)) - (printf "\n" - commit1 branch1 commit2 branch2) - (printf "\n" - points1 points2) - (printf "\n" - iterations1 iterations2) - (printf "\n" - seed1 seed2) - (printf "") - (printf "
      Date:~a
      ~a
      Commit:~a on ~a
      ~a on ~a
      Points:~a
      ~a
      Fuel:~a
      ~a
      Seed:~a
      ~a
      Flags:") - (for ([rec (hash->list flags1)]) - (for ([fl (cdr rec)]) - (printf "~a:~a" (car rec) fl))) - ;; (printf "

      vs.

      ") - (printf "

      ") - (for ([rec (hash->list flags2)]) - (for ([fl (cdr rec)]) - (printf "~a:~a" (car rec) fl))) - (printf "
      \n") - - ; Results table - (printf "\n") - (printf "") - (for ([label table-labels]) - (printf "" label)) - (printf "\n") - (printf "\n") - - (printf "") - (for ([name (remove-duplicates (map table-row-name (append tests1 tests2)))] - [id (in-naturals)]) - (define result1 (findf (compose (curry equal? name) table-row-name) - tests1)) - (define result2 (findf (compose (curry equal? name) table-row-name) - tests2)) - (printf "") - - (printf "" (html-escape-unsafe name)) - - ;; Some helper functions for displaying the different boxes for results - (define (format-bits-vs-other bits other) - (cond [(and (not bits) other) - (printf "" (format-bits other))] - [(and bits (not other)) - (printf "" (format-bits bits))] - [(and (not bits) (not other)) - (printf "")] - [((abs (- bits other)) . > . 1) - (printf "" - (format-bits bits) - (format-bits other))] - [#t - (printf "" - (format-bits bits))])) - - (define (format-bits-vs-est result est-result status) - (if (and result est-result - (> (abs (- result est-result)) 1)) - (printf "" - status (format-bits est-result) (format-bits result)) - (printf "" - status (format-bits result)))) - - (define (display-num-infs inf- inf+) - (printf "" - (if (and inf- (> inf- 0)) (format "+~a" inf-) "") - (if (and inf+ (> inf+ 0)) (format "-~a" inf+) ""))) - - ;; The starting bits - (format-bits-vs-other (table-row-start result1) (table-row-start result2)) - - ;; The first result bits box - (format-bits-vs-est (table-row-result result1) (table-row-result-est result1) - (table-row-status result1)) - ;; The second result bits box - (format-bits-vs-est (table-row-result result2) (table-row-result-est result2) - (table-row-status result2)) - - ;; The target bits - (format-bits-vs-other (table-row-target result1) (table-row-target result2)) - - ;; The number of points that went to infinity and back - (display-num-infs (table-row-inf- result1) (table-row-inf+ result1)) - (display-num-infs (table-row-inf- result2) (table-row-inf+ result2)) - - ;; The time each run of the test took. - (printf "" (format-time (table-row-time result1))) - (printf "" (format-time (table-row-time result2))) - - (printf "\n")) - (printf "\n") - (printf "
      ~a
      ~a~a~a~a/~a~a[~a ≉] ~a ~a~a~a~a~a
      \n") - (printf "\n") - (printf "\n")) + ;; Some helper functions for displaying the different boxes for results + (define (format-bits-vs-other bits other) + (cond [(and (not bits) other) + `(td ,(format-bits other))] + [(and bits (not other)) + `(td ,(format-bits bits))] + [(and (not bits) (not other)) + `(td)] + [((abs (- bits other)) . > . 1) + `(td ,(format-bits bits) "/" ,(format-bits other))] + [#t + `(td ,(format-bits bits))])) + + (define (format-bits-vs-est result est-result status) + (if (and result est-result + (> (abs (- result est-result)) 1)) + `(td ([class ,(format "bad-est" status)]) + "[" ,(format-bits est-result) " ≠] " ,(format-bits result)) + `(td ([class ,(~a status)]) ,(format-bits result)))) + + (define (display-num-infs inf- inf+) + `(td ([class "infs"]) + ,(if (and inf- (> inf- 0)) (format "+~a" inf-) "") + ,(if (and inf+ (> inf+ 0)) (format "-~a" inf+) ""))) + + `(tr + (td ,(~a name)) + ;; The starting bits + ,(format-bits-vs-other (table-row-start result1) (table-row-start result2)) + ;; The first result bits box + ,(format-bits-vs-est (table-row-result result1) (table-row-result-est result1) + (table-row-status result1)) + ;; The second result bits box + ,(format-bits-vs-est (table-row-result result2) (table-row-result-est result2) + (table-row-status result2)) + ;; The target bits + ,(format-bits-vs-other (table-row-target result1) (table-row-target result2)) + ;; The number of points that went to infinity and back + + ,(display-num-infs (table-row-inf- result1) (table-row-inf+ result1)) + ,(display-num-infs (table-row-inf- result2) (table-row-inf+ result2)) + (td ,(format-time (table-row-time result1))) + (td ,(format-time (table-row-time result2))))))))))) ; Delete old files (let* ([expected-dirs (map string->path (filter identity (map table-row-link tests1)))] diff --git a/src/reports/rerun.rkt b/src/reports/rerun.rkt index de08f370a..489e3f50d 100644 --- a/src/reports/rerun.rkt +++ b/src/reports/rerun.rkt @@ -21,7 +21,7 @@ (define tests (for/list ([row (report-info-tests data)]) (test (table-row-name row) (table-row-vars row) - (table-row-samplers row) (table-row-input row) (table-row-output row) #f #t 'TRUE))) + (table-row-input row) (table-row-output row) #f #t 'TRUE))) (*flags* (report-info-flags data)) (set-seed! (report-info-seed data)) (*num-points* (report-info-points data)) @@ -29,7 +29,7 @@ (define results (get-test-results tests #:threads threads #:dir dir #:seed (get-seed) #:profile profile?)) - (define info (make-report-info (filter identity results))) + (define info (make-report-info (map cdr results))) (write-datafile (build-path dir "results.json") info) (make-report-page (build-path dir "report.html") info) diff --git a/src/reports/run.rkt b/src/reports/run.rkt index c848bcbdb..49515dec2 100644 --- a/src/reports/run.rkt +++ b/src/reports/run.rkt @@ -23,7 +23,7 @@ (define tests (allowed-tests bench-dirs)) (define results (get-test-results tests #:threads threads #:seed seed #:profile profile? #:dir dir)) - (define info (make-report-info (filter identity results) #:note note #:seed seed)) + (define info (make-report-info (map cdr (filter values results)) #:note note #:seed seed)) (write-datafile (build-path dir "results.json") info) (make-report-page (build-path dir "report.html") info) @@ -55,7 +55,7 @@ #:once-each [("--timeout") s "Timeout for each test (in seconds)" (*timeout* (* 1000 (string->number s)))] - [("-r" "--seed") rs "The random seed vector to use in point generation" + [("-r" "--seed") rs "The random seed to use in point generation" (set-seed! (read (open-input-string rs)))] [("-p" "--profile") "Whether to profile each test" (set! profile? #t)] diff --git a/src/reports/thread-pool.rkt b/src/reports/thread-pool.rkt index f56f8a851..48874e25a 100644 --- a/src/reports/thread-pool.rkt +++ b/src/reports/thread-pool.rkt @@ -20,7 +20,10 @@ (set-seed! seed) (write-file (build-path rdir "graph.html") ((cond [(test-result? result) - (λ args (apply make-graph args) (apply make-plots args))] + (λ args + (define valid-js (apply make-interactive-js args)) + (apply make-graph (append args (list valid-js))) + (apply make-plots args))] [(test-timeout? result) make-timeout] [(test-failure? result) make-traceback]) result rdir profile?)))) @@ -90,17 +93,20 @@ `(done ,id ,self ,result)))]) (loop seed profile? dir)))) -(define (print-test-result tr) +(define (print-test-result data) + (match-define (cons fpcore tr) data) (match (table-row-status tr) + ["error" + (eprintf "[ ERROR ]\t~a\n" (table-row-name tr))] ["crash" - (printf "[ CRASH ]\t~a\n" (table-row-name tr))] + (eprintf "[ CRASH ]\t~a\n" (table-row-name tr))] ["timeout" - (printf "[ timeout ]\t~a\n" (table-row-name tr))] + (eprintf "[ timeout ]\t~a\n" (table-row-name tr))] [_ - (printf "[ ~ams]\t(~a→~a)\t~a\n" (~a (table-row-time tr) #:width 8) - (~r (table-row-start tr) #:min-width 2 #:precision 0) - (~r (table-row-result tr) #:min-width 2 #:precision 0) - (table-row-name tr))])) + (eprintf "[ ~ams]\t(~a→~a)\t~a\n" (~a (table-row-time tr) #:width 8) + (~r (table-row-start tr) #:min-width 2 #:precision 0) + (~r (table-row-result tr) #:min-width 2 #:precision 0) + (table-row-name tr))])) (define (run-workers progs threads #:seed seed #:profile profile? #:dir dir) (define config @@ -123,8 +129,7 @@ (for/list ([id (in-naturals)] [prog progs]) (list id prog))) - (printf "Starting ~a Herbie workers on ~a problems...\n" threads (length progs)) - (printf "Seed: ~a\n" seed) + (eprintf "Starting ~a Herbie workers on ~a problems (seed: ~a)...\n" threads (length progs) seed) (for ([worker workers]) (place-channel-put worker `(apply ,worker ,@(car work))) (set! work (cdr work))) @@ -133,8 +138,8 @@ (let loop ([out '()]) (with-handlers ([exn:break? (λ (_) - (printf "Terminating after ~a problem~a!\n" - (length out) (if (= (length out) 1) "s" "")) + (eprintf "Terminating after ~a problem~a!\n" + (length out) (if (= (length out) 1) "" "s")) out)]) (match-define `(done ,id ,more ,tr) (apply sync workers)) @@ -144,7 +149,7 @@ (define out* (cons (cons id tr) out)) - (printf "~a/~a\t" (~a (length out*) #:width 3 #:align 'right) (length progs)) + (eprintf "~a/~a\t" (~a (length out*) #:width 3 #:align 'right) (length progs)) (print-test-result tr) (if (= (length out*) (length progs)) @@ -156,21 +161,24 @@ outs) (define (run-nothreads progs #:seed seed #:profile profile? #:dir dir) - (printf "Starting Herbie on ~a problems...\n" (length progs)) - (printf "Seed: ~a\n" seed) + (eprintf "Starting Herbie on ~a problems (seed: ~a)...\n" (length progs) seed) (define out '()) (with-handlers ([exn:break? (λ (_) - (printf "Terminating after ~a problem~a!\n" + (eprintf "Terminating after ~a problem~a!\n" (length out) (if (= (length out) 1) "s" "")))]) (for ([test progs] [i (in-naturals)]) (define tr (run-test i test #:seed seed #:profile profile? #:dir dir)) - (printf "~a/~a\t" (~a (+ 1 i) #:width 3 #:align 'right) (length progs)) + (eprintf "~a/~a\t" (~a (+ 1 i) #:width 3 #:align 'right) (length progs)) (print-test-result tr) (set! out (cons (cons i tr) out)))) out) -(define (get-test-results progs #:threads [threads #f] #:seed seed #:profile [profile? #f] #:dir dir) +(define/contract (get-test-results progs #:threads threads #:seed seed #:profile profile? #:dir dir) + (-> (listof test?) #:threads (or/c #f natural-number/c) + #:seed (or/c pseudo-random-generator-vector? (integer-in 1 (sub1 (expt 2 31)))) + #:profile boolean? #:dir (or/c #f path-string?) + (listof (or/c #f (cons/c expr? table-row?)))) (when (and threads (> threads (length progs))) (set! threads (length progs))) @@ -183,6 +191,4 @@ (for ([(idx result) (in-dict outs)]) (vector-set! out idx result)) - ; The use of > instead of < is a cleverness: - ; the list of tests is accumulated in reverse, this reverses again. (vector->list out)) diff --git a/src/sandbox.rkt b/src/sandbox.rkt index beef4e71a..b996611da 100644 --- a/src/sandbox.rkt +++ b/src/sandbox.rkt @@ -3,7 +3,7 @@ (require math/bigfloat) (require racket/engine) -(require "common.rkt") +(require "common.rkt" "errors.rkt") (require "debug.rkt") (require "mainloop.rkt") (require "formats/datafile.rkt") @@ -15,7 +15,7 @@ (provide get-test-result *reeval-pts* *timeout* (struct-out test-result) (struct-out test-failure) (struct-out test-timeout) - get-table-data) + get-table-data unparse-result) ; For things that don't leave a thread @@ -44,17 +44,18 @@ (define (compute-result test) (parameterize ([*debug-port* (or debug? (*debug-port*))]) (when seed (set-seed! seed)) + (random) ;; Child process uses deterministic but different seed from evaluator (when setup! (setup!)) (with-handlers ([exn? on-error]) (match-define (list alt context) (run-improve (test-program test) (*num-iterations*) #:get-context #t - #:samplers (test-samplers test) #:precondition (test-precondition test))) + (when seed (set-seed! seed)) (define newcontext (parameterize ([*num-points* (*reeval-pts*)]) - (prepare-points (alt-program alt) (test-samplers test) (test-precondition test)))) + (prepare-points (test-program test) (test-precondition test)))) `(good ,(make-alt (test-program test)) ,alt ,context ,newcontext ,(^timeline^) ,(bf-precision))))) @@ -90,6 +91,9 @@ (test-timeout test (bf-precision) (*timeout*) (^timeline^))]))) (define (get-table-data result link) + (cons (unparse-result result) (get-table-data* result link))) + +(define (get-table-data* result link) (cond [(test-result? result) (let* ([name (test-name (test-result-test result))] @@ -104,7 +108,7 @@ [est-start-score (errors-score (test-result-start-est-error result))] [est-end-score (errors-score (test-result-end-est-error result))]) - (let*-values ([(reals infs) (partition ordinary-float? (map - end-errors start-errors))] + (let*-values ([(reals infs) (partition ordinary-value? (map - end-errors start-errors))] [(good-inf bad-inf) (partition positive? infs)]) (table-row name (if target-score @@ -124,9 +128,9 @@ (and target-score target-score) (length good-inf) (length bad-inf) + est-start-score est-end-score (program-variables (alt-program (test-result-start-alt result))) - (test-sampling-expr (test-result-test result)) (program-body (alt-program (test-result-start-alt result))) (program-body (alt-program (test-result-end-alt result))) (test-result-time result) @@ -134,11 +138,65 @@ link)))] [(test-failure? result) (define test (test-failure-test result)) - (table-row (test-name test) "crash" - #f #f #f #f #f #f (test-vars test) (test-sampling-expr test) (test-input test) #f + (table-row (test-name test) (if (exn:fail:user:herbie? (test-failure-exn result)) "error" "crash") + #f #f #f #f #f #f #f (test-vars test) (test-input test) #f (test-failure-time result) (test-failure-bits result) link)] [(test-timeout? result) (define test (test-timeout-test result)) (table-row (test-name (test-timeout-test result)) "timeout" - #f #f #f #f #f #f (test-vars test) (test-sampling-expr test) (test-input test) #f + #f #f #f #f #f #f #f (test-vars test) (test-input test) #f (test-timeout-time result) (test-timeout-bits result) link)])) + +(define (unparse-result result) + (match result + [(test-result test time bits + start-alt end-alt points exacts start-est-error end-est-error + newpoints newexacts start-error end-error target-error timeline) + `(FPCore ,(test-vars test) + :herbie-status success + :herbie-time ,time + :herbie-bits-used ,bits + :herbie-error-input + ([,(*num-points*) ,(errors-score start-est-error)] + [,(*reeval-pts*) ,(errors-score start-error)]) + :herbie-error-output + ([,(*num-points*) ,(errors-score end-est-error)] + [,(*reeval-pts*) ,(errors-score end-error)]) + ,@(if target-error + `(:herbie-error-target + ([,(*reeval-pts*) ,(errors-score target-error)])) + '()) + :name ,(test-name test) + ,@(if (eq? (test-precondition test) 'TRUE) + '() + `(:pre ,(test-precondition test))) + ,@(if (test-output test) + `(:herbie-target ,(test-output test)) + '()) + ,(program-body (alt-program end-alt)))] + [(test-failure test bits exn time timeline) + `(FPCore ,(test-vars test) + :herbie-status ,(if (exn:fail:user:herbie? (test-failure-exn result)) 'error 'crash) + :herbie-time ,time + :herbie-bits-used ,bits + :name ,(test-name test) + ,@(if (eq? (test-precondition test) 'TRUE) + '() + `(:pre ,(test-precondition test))) + ,@(if (test-output test) + `(:herbie-target ,(test-output test)) + '()) + ,(test-input test))] + [(test-timeout test bits time timeline) + `(FPCore ,(test-vars test) + :herbie-status timeout + :herbie-time ,time + :herbie-bits-used ,bits + :name ,(test-name test) + ,@(if (eq? (test-precondition test) 'TRUE) + '() + `(:pre ,(test-precondition test))) + ,@(if (test-output test) + `(:herbie-target ,(test-output test)) + '()) + ,(test-input test))])) diff --git a/src/shell.rkt b/src/shell.rkt index a0bdbac20..4c9674b9d 100644 --- a/src/shell.rkt +++ b/src/shell.rkt @@ -17,13 +17,15 @@ (define (run-shell) (define seed (get-seed)) - (eprintf "Seed: ~a\n" seed) + (eprintf "Herbie ~a with seed ~a\n" *herbie-version* seed) + (eprintf "Find help on , exit with ~a\n" + (match (system-type 'os) ['windows "Ctrl-Z Enter"] [_ "Ctrl-D"])) (with-handlers ([exn:break? (λ (e) (exit 0))]) (for ([test (in-producer get-input eof-object?)] [idx (in-naturals)]) (define output (get-test-result test #:seed seed)) (match output [(? test-result?) - (printf "~a\n" (unparse-test (alt-program (test-result-end-alt output))))] + (printf "~a\n" (unparse-result output))] [(test-failure test bits exn time timeline) ((error-display-handler) (exn-message exn) exn)] [(test-timeout test bits time timeline) diff --git a/src/syntax-check.rkt b/src/syntax-check.rkt new file mode 100644 index 000000000..f4826b58c --- /dev/null +++ b/src/syntax-check.rkt @@ -0,0 +1,117 @@ +#lang racket + +(require syntax/id-set) +(require "common.rkt" "syntax/syntax.rkt" "errors.rkt") +(provide assert-expression! assert-program!) + +(define (check-expression* stx vars error!) + (match stx + [#`,(? constant?) (void)] + [#`,(? variable? var) + (unless (set-member? vars stx) + (error! stx "Unknown variable ~a" var))] + [#`(let ((#,vars* #,vals) ...) #,body) + ;; These are unfolded by desugaring + (for ([var vars*] [val vals]) + (unless (identifier? var) + (error! var "Invalid variable name ~a" var)) + (check-expression* val vars error!)) + (check-expression* body (bound-id-set-union vars (immutable-bound-id-set vars*)) error!)] + [#`(,(? (curry set-member? '(+ - * /))) #,args ...) + ;; These expand associativity so we don't check the number of arguments + (for ([arg args]) (check-expression* arg vars error!))] + [#`(,(and (or 'sqr 'cube) f) #,args ...) + (unless (= (length args) 1) + (error! stx "Operator ~a given ~a arguments (expects 1)" f (length args))) + (eprintf "Warning: the `sqr` and `cube` operators are deprecated and will be removed in later versions.\n") + (for ([arg args]) (check-expression* arg vars error!))] + [#`(,f #,args ...) + (if (operator? f) + (let ([num-args (operator-info f 'args)]) + (unless (or (set-member? num-args (length args)) (set-member? num-args '*)) + (error! stx "Operator ~a given ~a arguments (expects ~a)" + f (length args) (string-join (map ~a num-args) " or ")))) + (error! stx "Unknown operator ~a" f)) + (for ([arg args]) (check-expression* arg vars error!))] + [_ (error! stx "Unknown syntax ~a" stx)])) + +(define (check-property* prop error!) + (unless (identifier? prop) + (error! prop "Invalid property name ~a" prop)) + (define name (~a (syntax-e prop))) + (unless (equal? (substring name 0 1) ":") + (error! prop "Invalid property name ~a" prop))) + +(define (check-properties* props vars error!) + (define prop-dict + (let loop ([props props] [out '()]) + (match props + [(list (? identifier? prop-name) value rest ...) + (check-property* prop-name error!) + (loop rest (cons (cons (syntax-e prop-name) value) out))] + [(list head) + (check-property* head error!) + (error! head "Property ~a has no value" head) + out] + [(list) + out]))) + + (when (dict-has-key? prop-dict ':name) + (define name (dict-ref prop-dict ':name)) + (unless (string? (syntax-e name)) + (error! name "Invalid :name ~a; must be a string" name))) + + (when (dict-has-key? prop-dict ':description) + (define desc (dict-ref prop-dict ':description)) + (unless (string? (syntax-e desc)) + (error! desc "Invalid :description ~a; must be a string" desc))) + + (when (dict-has-key? prop-dict ':cite) + (define cite (dict-ref prop-dict ':cite)) + (unless (list? (syntax-e cite)) + (error! cite "Invalid :cite ~a; must be a list" cite)) + (when (list? (syntax-e cite)) + (for ([citation (syntax-e cite)] #:unless (identifier? citation)) + (error! citation "Invalid citation ~a; must be a variable name" citation)))) + + (when (dict-has-key? prop-dict ':pre) + (check-expression* (dict-ref prop-dict ':pre) vars error!)) + + (when (dict-has-key? prop-dict ':herbie-target) + (check-expression* (dict-ref prop-dict ':herbie-target) vars error!))) + +(define (check-program* stx error!) + (match stx + [#`(FPCore #,vars #,props ... #,body) + (unless (list? (syntax-e vars)) + (error! stx "Invalid arguments list ~a; must be a list" stx)) + (when (list? (syntax-e vars)) + (for ([var (syntax-e vars)] #:unless (identifier? var)) + (error! stx "Argument ~a is not a variable name" stx)) + (when (check-duplicate-identifier (syntax-e vars)) + (error! stx "Duplicate argument name ~a" + (check-duplicate-identifier (syntax-e vars))))) + (define vars* (immutable-bound-id-set (if (list? (syntax-e vars)) (syntax-e vars) '()))) + (check-properties* props vars* error!) + (check-expression* body vars* error!)] + [_ (error! stx "Unknown syntax ~a" stx)])) + +(define (assert-expression! stx vars) + (define errs + (reap [sow] + (define (error! stx fmt . args) + (define args* (map (λ (x) (if (syntax? x) (syntax->datum x) x)) args)) + (sow (cons stx (apply format fmt args*)))) + (check-expression* stx vars error!))) + (unless (null? errs) + (raise-herbie-syntax-error "Invalid expression" #:locations errs))) + +(define (assert-program! stx) + (define errs + (reap [sow] + (define (error! stx fmt . args) + (define args* (map (λ (x) (if (syntax? x) (syntax->datum x) x)) args)) + (sow (cons stx (apply format fmt args*)))) + (check-program* stx error!))) + (unless (null? errs) + (raise-herbie-syntax-error "Invalid program" #:locations errs))) diff --git a/src/syntax/distributions.rkt b/src/syntax/distributions.rkt deleted file mode 100644 index eb7a0cc23..000000000 --- a/src/syntax/distributions.rkt +++ /dev/null @@ -1,77 +0,0 @@ -#lang racket -(require "../common.rkt" "../range-analysis.rkt") -(require math/flonum) -(provide eval-sampler) - -(module+ test - (require rackunit)) - -(define (sample-float) - (floating-point-bytes->real (integer->integer-bytes (random-exp 32) 4 #f))) - -(define (sample-double) - (floating-point-bytes->real (integer->integer-bytes (random-exp 64) 8 #f))) - -(define (sample-default) - (((flag 'precision 'double) sample-double sample-float))) - -(define (sample-uniform a b) - (+ (* (random) (- b a)) a)) - -(define (sample-int) - (- (random-exp 32) (expt 2 31))) - -(define (sample-bounded lo hi #:left-closed? [left-closed? #t] #:right-closed? [right-closed? #t]) - (define lo* (exact->inexact lo)) - (define hi* (exact->inexact hi)) - (cond - [(> lo* hi*) #f] - [(= lo* hi*) - (if (and left-closed? right-closed?) lo* #f)] - [(< lo* hi*) - (define ordinal (- (flonum->ordinal hi*) (flonum->ordinal lo*))) - (define num-bits (ceiling (/ (log ordinal) (log 2)))) - (define random-num (random-exp (inexact->exact num-bits))) - (if (or (and (not left-closed?) (equal? 0 random-num)) - (and (not right-closed?) (equal? ordinal random-num)) - (> random-num ordinal)) - ;; Happens with p < .5 so will not loop forever - (sample-bounded lo hi #:left-closed? left-closed? #:right-closed? right-closed?) - (ordinal->flonum (+ (flonum->ordinal lo*) random-num)))])) - -(module+ test - (check-true (<= 1.0 (sample-bounded 1 2) 2.0)) - (let ([a (sample-bounded 1 2 #:left-closed? #f)]) - (check-true (< 1 a)) - (check-true (<= a 2))) - (check-false (sample-bounded 1 1.0 #:left-closed? #f) "Empty interval due to left openness") - (check-false (sample-bounded 1 1.0 #:right-closed? #f) "Empty interval due to right openness") - (check-false (sample-bounded 1 1.0 #:left-closed? #f #:right-closed? #f) - "Empty interval due to both-openness") - (check-false (sample-bounded 2.0 1.0) "Interval bounds flipped")) - -(define (eval-op op) - (match op ['> >] ['< <] ['>= >=] ['<= <=])) - -(define-match-expander op - (λ (stx) - (syntax-case stx () - [(_ val) - #'(and (or '< '> '>= '<=) (app eval-op val))]))) - -(define (eval-sampler expr [intval (interval -inf.0 +inf.0 #f #t)]) - (match-define (interval lo hi lo? hi?) intval) - (match expr - ['default (λ () (sample-bounded lo hi #:left-closed? lo? #:right-closed? hi?))] - [(? number? x) (const x)] - [`(uniform ,(? number? a) ,(? number? b)) (λ () (sample-uniform (max a lo) (min b hi)))] - ['int sample-int] - [(list (op op) (? number? lb) sub) - (define sub* (eval-sampler sub)) - (λ () (let ([y (sub*)]) (and (op lb y) y)))] - [(list (op op) sub (? number? ub)) - (define sub* (eval-sampler sub)) - (λ () (let ([y (sub*)]) (and (op y ub) y)))] - [(list (op op) (? number? lb) sub (? number? ub)) - (define sub* (eval-sampler sub)) - (λ () (let ([y (sub*)]) (and (op lb y ub) y)))])) diff --git a/src/syntax/rules.rkt b/src/syntax/rules.rkt index a91553e9b..63ed17336 100644 --- a/src/syntax/rules.rkt +++ b/src/syntax/rules.rkt @@ -3,8 +3,10 @@ ;; Arithmetic identities for rewriting programs. (require "../common.rkt") +(require "syntax.rkt") -(provide (struct-out rule) *rules* *simplify-rules* get-rule) +(provide (struct-out rule) *complex-rules* rule-valid-at-type? *rules* *simplify-rules* + *fp-safe-simplify-rules* prune-rules!) (struct rule (name input output) ; Input and output are patterns #:methods gen:custom-write @@ -15,23 +17,39 @@ (define *rulesets* (make-parameter '())) -(define-syntax-rule (define-ruleset name groups [rname input output] ...) - (begin (define name (list (rule 'rname 'input 'output) ...)) - (*rulesets* (cons (cons name 'groups) (*rulesets*))))) - -(define (get-rule name) - (let ([results (filter (λ (rule) (eq? (rule-name rule) name)) (*rules*))]) - (if (null? results) - (error "Could not find a rule by the name" name) - (car results)))) +(define (rule-ops-supported? rule) + (define (ops-in-expr expr) + (cond + [(list? expr) (if (set-member? (*loaded-ops*) (car expr)) + (for/and ([subexpr (cdr expr)]) + (ops-in-expr subexpr)) + #f)] + [else #t])) + (ops-in-expr (rule-output rule))) + +(define (prune-rules!) + (*rulesets* (for/list ([ruleset (*rulesets*)]) + (cons (for/list ([rule (car ruleset)] + #:when (rule-ops-supported? rule)) + rule) + (cdr ruleset))))) + +(define-syntax define-ruleset + (syntax-rules () + [(define-ruleset name groups [rname input output] ...) + (define-ruleset name groups #:type () [rname input output] ...)] + [(define-ruleset name groups #:type ([var type] ...) + [rname input output] ...) + (begin (define name (list (rule 'rname 'input 'output) ...)) + (*rulesets* (cons (list name 'groups '((var . type) ...)) (*rulesets*))))])) ; Commutativity -(define-ruleset commutativity (arithmetic simplify) +(define-ruleset commutativity (arithmetic simplify complex fp-safe) [+-commutative (+ a b) (+ b a)] [*-commutative (* a b) (* b a)]) ; Associativity -(define-ruleset associativity (arithmetic simplify) +(define-ruleset associativity (arithmetic simplify complex) [associate-+r+ (+ a (+ b c)) (+ (+ a b) c)] [associate-+l+ (+ (+ a b) c) (+ a (+ b c))] [associate-+r- (+ a (- b c)) (- (+ a b) c)] @@ -47,10 +65,12 @@ [associate-/r* (/ a (* b c)) (/ (/ a b) c)] [associate-/l* (/ (* b c) a) (/ b (/ a c))] [associate-/r/ (/ a (/ b c)) (* (/ a b) c)] - [associate-/l/ (/ (/ b c) a) (/ b (* a c))]) + [associate-/l/ (/ (/ b c) a) (/ b (* a c))] + [sub-neg (- a b) (+ a (- b))] + [unsub-neg (+ a (- b)) (- a b)]) ; Distributivity -(define-ruleset distributivity (arithmetic simplify) +(define-ruleset distributivity (arithmetic simplify complex) [distribute-lft-in (* a (+ b c)) (+ (* a b) (* a c))] [distribute-rgt-in (* a (+ b c)) (+ (* b a) (* c a))] [distribute-lft-out (+ (* a b) (* a c)) (* a (+ b c))] @@ -58,7 +78,10 @@ [distribute-rgt-out (+ (* b a) (* c a)) (* a (+ b c))] [distribute-rgt-out-- (- (* b a) (* c a)) (* a (- b c))] [distribute-lft1-in (+ (* b a) a) (* (+ b 1) a)] - [distribute-rgt1-in (+ a (* c a)) (* (+ c 1) a)] + [distribute-rgt1-in (+ a (* c a)) (* (+ c 1) a)]) + +; Safe Distributiviity +(define-ruleset distributivity-fp-safe (arithmetic simplify fp-safe) [distribute-lft-neg-in (- (* a b)) (* (- a) b)] [distribute-rgt-neg-in (- (* a b)) (* a (- b))] [distribute-lft-neg-out (* (- a) b) (- (* a b))] @@ -66,65 +89,70 @@ [distribute-neg-in (- (+ a b)) (+ (- a) (- b))] [distribute-neg-out (+ (- a) (- b)) (- (+ a b))] [distribute-frac-neg (/ (- a) b) (- (/ a b))] - [distribute-neg-frac (- (/ a b)) (/ (- a) b)] - [sum-double (+ a a) (* 2 a)] - [double-sum (* 2 a) (+ a a)]) - + [distribute-neg-frac (- (/ a b)) (/ (- a) b)]) ; Difference of squares (define-ruleset difference-of-squares-canonicalize (polynomials simplify) - [difference-of-squares (- (sqr a) (sqr b)) (* (+ a b) (- a b))] - [difference-of-sqr-1 (- (sqr a) 1) (* (+ a 1) (- a 1))] - [difference-of-sqr--1 (+ (sqr a) -1) (* (+ a 1) (- a 1))]) + [difference-of-squares (- (* a a) (* b b)) (* (+ a b) (- a b))] + [difference-of-sqr-1 (- (* a a) 1) (* (+ a 1) (- a 1))] + [difference-of-sqr--1 (+ (* a a) -1) (* (+ a 1) (- a 1))]) (define-ruleset difference-of-squares-flip (polynomials) - [flip-+ (+ a b) (/ (- (sqr a) (sqr b)) (- a b))] - [flip-- (- a b) (/ (- (sqr a) (sqr b)) (+ a b))]) + [flip-+ (+ a b) (/ (- (* a a) (* b b)) (- a b))] + [flip-- (- a b) (/ (- (* a a) (* b b)) (+ a b))]) ; Identity (define-ruleset id-reduce (arithmetic simplify) + [remove-double-div (/ 1 (/ 1 a)) a] + [rgt-mult-inverse (* a (/ 1 a)) 1] + [lft-mult-inverse (* (/ 1 a) a) 1]) + +(define-ruleset id-reduce-fp-safe-nan (arithmetic simplify fp-safe-nan) + [+-inverses (- a a) 0] + [*-inverses (/ a a) 1] + [div0 (/ 0 a) 0] + [mul0 (* 0 a) 0] + [mul0 (* a 0) 0]) + +(define-ruleset id-reduce-fp-safe (arithmetic simplify fp-safe) [+-lft-identity (+ 0 a) a] [+-rgt-identity (+ a 0) a] - [+-inverses (- a a) 0] + [--rgt-identity (- a 0) a] [sub0-neg (- 0 b) (- b)] [remove-double-neg (- (- a)) a] [*-lft-identity (* 1 a) a] [*-rgt-identity (* a 1) a] - [*-inverses (/ a a) 1] - [remove-double-div (/ 1 (/ 1 a)) a] - [rgt-mult-inverse (* a (/ 1 a)) 1] - [lft-mult-inverse (* (/ 1 a) a) 1] - [div0 (/ 0 a) 0] - [mul0 (* 0 a) 0] - [mul0 (* a 0) 0] + [/-rgt-identity (/ a 1) a] [mul-1-neg (* -1 a) (- a)]) (define-ruleset id-transform (arithmetic) + [div-inv (/ a b) (* a (/ 1 b))] + [un-div-inv (* a (/ 1 b)) (/ a b)] + [clear-num (/ a b) (/ 1 (/ b a))]) + +(define-ruleset id-transform-fp-safe (arithmetic fp-safe) [sub-neg (- a b) (+ a (- b))] [unsub-neg (+ a (- b)) (- a b)] [neg-sub0 (- b) (- 0 b)] [*-un-lft-identity a (* 1 a)] - [div-inv (/ a b) (* a (/ 1 b))] - [un-div-inv (* a (/ 1 b)) (/ a b)] - [neg-mul-1 (- a) (* -1 a)] - [clear-num (/ a b) (/ 1 (/ b a))]) + [neg-mul-1 (- a) (* -1 a)]) ; Difference of cubes (define-ruleset difference-of-cubes (polynomials) [sum-cubes (+ (pow a 3) (pow b 3)) - (* (+ (sqr a) (- (sqr b) (* a b))) (+ a b))] + (* (+ (* a a) (- (* b b) (* a b))) (+ a b))] [difference-cubes (- (pow a 3) (pow b 3)) - (* (+ (sqr a) (+ (sqr b) (* a b))) (- a b))] + (* (+ (* a a) (+ (* b b) (* a b))) (- a b))] [flip3-+ (+ a b) - (/ (+ (pow a 3) (pow b 3)) (+ (sqr a) (- (sqr b) (* a b))))] + (/ (+ (pow a 3) (pow b 3)) (+ (* a a) (- (* b b) (* a b))))] [flip3-- (- a b) - (/ (- (pow a 3) (pow b 3)) (+ (sqr a) (+ (sqr b) (* a b))))]) + (/ (- (pow a 3) (pow b 3)) (+ (* a a) (+ (* b b) (* a b))))]) ; Dealing with fractions -(define-ruleset fractions-distribute (fractions simplify) +(define-ruleset fractions-distribute (fractions simplify complex) [div-sub (/ (- a b) c) (- (/ a c) (/ b c))] [times-frac (/ (* a b) (* c d)) (* (/ a c) (/ b d))]) -(define-ruleset fractions-transform (fractions) +(define-ruleset fractions-transform (fractions complex) [sub-div (- (/ a c) (/ b c)) (/ (- a b) c)] [frac-add (+ (/ a b) (/ c d)) (/ (+ (* a d) (* b c)) (* b d))] [frac-sub (- (/ a b) (/ c d)) (/ (- (* a d) (* b c)) (* b d))] @@ -133,13 +161,15 @@ ; Square root (define-ruleset squares-reduce (arithmetic simplify) - [rem-square-sqrt (sqr (sqrt x)) x] - [rem-sqrt-square (sqrt (sqr x)) (fabs x)] - [sqr-neg (sqr (- x)) (sqr x)]) + [rem-square-sqrt (* (sqrt x) (sqrt x)) x] + [rem-sqrt-square (sqrt (* x x)) (fabs x)]) + +(define-ruleset squares-reduce-fp-sound (arithmetic simplify fp-sound) + [sqr-neg (* (- x) (- x)) (* x x)]) (define-ruleset squares-distribute (arithmetic simplify) - [square-prod (sqr (* x y)) (* (sqr x) (sqr y))] - [square-div (sqr (/ x y)) (/ (sqr x) (sqr y))] + [square-prod (sqr (* x y)) (* (* x x) (* y y))] + [square-div (sqr (/ x y)) (/ (* x x) (* y y))] [square-mult (sqr x) (* x x)]) (define-ruleset squares-transform (arithmetic) @@ -147,36 +177,29 @@ [sqrt-div (sqrt (/ x y)) (/ (sqrt x) (sqrt y))] [sqrt-unprod (* (sqrt x) (sqrt y)) (sqrt (* x y))] [sqrt-undiv (/ (sqrt x) (sqrt y)) (sqrt (/ x y))] - [add-sqr-sqrt x (sqr (sqrt x))] - [square-unprod (* (sqr x) (sqr y)) (sqr (* x y))] - [square-undiv (/ (sqr x) (sqr y)) (sqr (/ x y))]) - -(define-ruleset squares-canonicalize (arithmetic simplify) - [square-unmult (* x x) (sqr x)]) + [add-sqr-sqrt x (* (sqrt x) (sqrt x))]) ; Cube root (define-ruleset cubes-reduce (arithmetic simplify) - [rem-cube-cbrt (cube (cbrt x)) x] - [rem-cbrt-cube (cbrt (cube x)) x] - [cube-neg (cube (- x)) (- (cube x))]) + [rem-cube-cbrt (pow (cbrt x) 3) x] + [rem-cbrt-cube (cbrt (pow x 3)) x] + [cube-neg (pow (- x) 3) (- (pow x 3))]) (define-ruleset cubes-distribute (arithmetic simplify) - [cube-prod (cube (* x y)) (* (cube x) (cube y))] - [cube-div (cube (/ x y)) (/ (cube x) (cube y))] - [cube-mult (cube x) (* x (* x x))]) + [cube-prod (pow (* x y) 3) (* (pow x 3) (pow y 3))] + [cube-div (pow (/ x y) 3) (/ (pow x 3) (pow y 3))] + [cube-mult (pow x 3) (* x (* x x))]) (define-ruleset cubes-transform (arithmetic) - [cbrt-prod (cbrt (* x y)) (* (cbrt x) (cbrt y))] - [cbrt-div (cbrt (/ x y)) (/ (cbrt x) (cbrt y))] - [cbrt-unprod (* (cbrt x) (cbrt y)) (cbrt (* x y))] - [cbrt-undiv (/ (cbrt x) (cbrt y)) (cbrt (/ x y))] - [add-cube-cbrt x (cube (cbrt x))] - [add-cbrt-cube x (cbrt (cube x))] - [cube-unprod (* (cube x) (cube y)) (cube (* x y))] - [cube-undiv (/ (cube x) (cube y)) (cube (/ x y))]) + [cbrt-prod (cbrt (* x y)) (* (cbrt x) (cbrt y))] + [cbrt-div (cbrt (/ x y)) (/ (cbrt x) (cbrt y))] + [cbrt-unprod (* (cbrt x) (cbrt y)) (cbrt (* x y))] + [cbrt-undiv (/ (cbrt x) (cbrt y)) (cbrt (/ x y))] + [add-cube-cbrt x (* (* (cbrt x) (cbrt x)) (cbrt x))] + [add-cbrt-cube x (cbrt (* (* x x) x))]) (define-ruleset cubes-canonicalize (arithmetic simplify) - [cube-unmult (* x (* x x)) (cube x)]) + [cube-unmult (* x (* x x)) (pow x 3)]) ; Exponentials (define-ruleset exp-expand (exponents) @@ -185,12 +208,15 @@ (define-ruleset exp-reduce (exponents simplify) [rem-exp-log (exp (log x)) x] - [rem-log-exp (log (exp x)) x] + [rem-log-exp (log (exp x)) x]) + +(define-ruleset exp-reduce-fp-safe (exponents simplify fp-safe) [exp-0 (exp 0) 1] [1-exp 1 (exp 0)] [exp-1-e (exp 1) E] [e-exp-1 E (exp 1)]) + (define-ruleset exp-distribute (exponents simplify) [exp-sum (exp (+ a b)) (* (exp a) (exp b))] [exp-neg (exp (- a)) (/ 1 (exp a))] @@ -203,25 +229,30 @@ [exp-prod (exp (* a b)) (pow (exp a) b)] [exp-sqrt (exp (/ a 2)) (sqrt (exp a))] [exp-cbrt (exp (/ a 3)) (cbrt (exp a))] - [exp-lft-sqr (exp (* a 2)) (sqr (exp a))] - [exp-lft-cube (exp (* a 3)) (cube (exp a))]) + [exp-lft-sqr (exp (* a 2)) (* (exp a) (exp a))] + [exp-lft-cube (exp (* a 3)) (pow (exp a) 3)]) ; Powers (define-ruleset pow-reduce (exponents simplify) - [unpow-1 (pow a -1) (/ 1 a)] - [unpow1 (pow a 1) a] - [unpow0 (pow a 0) 1]) + [unpow-1 (pow a -1) (/ 1 a)]) + +(define-ruleset pow-reduce-fp-safe (exponents simplify fp-safe) + [unpow1 (pow a 1) a]) + +(define-ruleset pow-reduce-fp-safe-nan (exponents simplify fp-safe-nan) + [unpow0 (pow a 0) 1] + [pow-base-1 (pow 1 a) 1]) -(define-ruleset pow-expand (exponents) +(define-ruleset pow-expand-fp-safe (exponents fp-safe) [pow1 a (pow a 1)]) (define-ruleset pow-canonicalize (exponents simplify) [exp-to-pow (exp (* (log a) b)) (pow a b)] [pow-plus (* (pow a b) a) (pow a (+ b 1))] - [unpow2 (pow a 2) (sqr a)] [unpow1/2 (pow a 1/2) (sqrt a)] - [unpow3 (pow a 3) (cube a)] - [unpow1/3 (pow a 1/3) (cbrt a)] ) + [unpow2 (pow a 2) (* a a)] + [unpow3 (pow a 3) (* (* a a) a)] + [unpow1/3 (pow a 1/3) (cbrt a)]) (define-ruleset pow-transform (exponents) [pow-exp (pow (exp a) b) (exp (* a b))] @@ -236,18 +267,25 @@ [pow-unpow (pow a (* b c)) (pow (pow a b) c)] [unpow-prod-up (pow a (+ b c)) (* (pow a b) (pow a c))] [unpow-prod-down (pow (* b c) a) (* (pow b a) (pow c a))] - [inv-pow (/ 1 a) (pow a -1)] [pow1/2 (sqrt a) (pow a 1/2)] - [pow2 (sqr a) (pow a 2)] + [pow2 (* a a) (pow a 2)] [pow1/3 (cbrt a) (pow a 1/3)] - [pow3 (cube a) (pow a 3)]) + [pow3 (* (* a a) a) (pow a 3)]) + +(define-ruleset pow-transform-fp-safe-nan (exponents fp-safe-nan) + [pow-base-0 (pow 0 a) 0]) + +(define-ruleset pow-transform-fp-safe (exponents fp-safe) + [inv-pow (/ 1 a) (pow a -1)]) ; Logarithms (define-ruleset log-distribute (exponents simplify) [log-prod (log (* a b)) (+ (log a) (log b))] [log-div (log (/ a b)) (- (log a) (log b))] [log-rec (log (/ 1 a)) (- (log a))] - [log-pow (log (pow a b)) (* b (log a))] + [log-pow (log (pow a b)) (* b (log a))]) + +(define-ruleset log-distribute-fp-safe (exponents simplify fp-safe) [log-E (log E) 1]) (define-ruleset log-factor (exponents) @@ -257,15 +295,13 @@ ; Trigonometry (define-ruleset trig-reduce (trigonometry simplify) - [cos-sin-sum (+ (sqr (cos a)) (sqr (sin a))) 1] - [1-sub-cos (- 1 (sqr (cos a))) (sqr (sin a))] - [1-sub-sin (- 1 (sqr (sin a))) (sqr (cos a))] - [-1-add-cos (+ (sqr (cos a)) -1) (- (sqr (sin a)))] - [-1-add-sin (+ (sqr (sin a)) -1) (- (sqr (cos a)))] - [sub-1-cos (- (sqr (cos a)) 1) (- (sqr (sin a)))] - [sub-1-sin (- (sqr (sin a)) 1) (- (sqr (cos a)))] - [sin-neg (sin (- x)) (- (sin x))] - [sin-0 (sin 0) 0] + [cos-sin-sum (+ (* (cos a) (cos a)) (* (sin a) (sin a))) 1] + [1-sub-cos (- 1 (* (cos a) (cos a))) (* (sin a) (sin a))] + [1-sub-sin (- 1 (* (sin a) (sin a))) (* (cos a) (cos a))] + [-1-add-cos (+ (* (cos a) (cos a)) -1) (- (* (sin a) (sin a)))] + [-1-add-sin (+ (* (sin a) (sin a)) -1) (- (* (cos a) (cos a)))] + [sub-1-cos (- (* (cos a) (cos a)) 1) (- (* (sin a) (sin a)))] + [sub-1-sin (- (* (sin a) (sin a)) 1) (- (* (cos a) (cos a)))] [sin-PI/6 (sin (/ PI 6)) 1/2] [sin-PI/4 (sin (/ PI 4)) (/ (sqrt 2) 2)] [sin-PI/3 (sin (/ PI 3)) (/ (sqrt 3) 2)] @@ -273,8 +309,6 @@ [sin-PI (sin PI) 0] [sin-+PI (sin (+ x PI)) (- (sin x))] [sin-+PI/2 (sin (+ x (/ PI 2))) (cos x)] - [cos-neg (cos (- x)) (cos x)] - [cos-0 (cos 0) 1] [cos-PI/6 (cos (/ PI 6)) (/ (sqrt 3) 2)] [cos-PI/4 (cos (/ PI 4)) (/ (sqrt 2) 2)] [cos-PI/3 (cos (/ PI 3)) 1/2] @@ -282,56 +316,86 @@ [cos-PI (cos PI) -1] [cos-+PI (cos (+ x PI)) (- (cos x))] [cos-+PI/2 (cos (+ x (/ PI 2))) (- (sin x))] - [tan-neg (tan (- x)) (- (tan x))] - [tan-0 (tan 0) 0] [tan-PI/6 (tan (/ PI 6)) (/ 1 (sqrt 3))] [tan-PI/4 (tan (/ PI 4)) 1] [tan-PI/3 (tan (/ PI 3)) (sqrt 3)] [tan-PI (tan PI) 0] [tan-+PI (tan (+ x PI)) (tan x)] - [tan-+PI/2 (tan (+ x (/ PI 2))) (- (/ 1 (tan x)))]) + [tan-+PI/2 (tan (+ x (/ PI 2))) (- (/ 1 (tan x)))] + [hang-0p-tan (/ (sin a) (+ 1 (cos a))) (tan (/ a 2))] + [hang-0m-tan (/ (- (sin a)) (+ 1 (cos a))) (tan (/ (- a) 2))] + [hang-p0-tan (/ (- 1 (cos a)) (sin a)) (tan (/ a 2))] + [hang-m0-tan (/ (- 1 (cos a)) (- (sin a))) (tan (/ (- a) 2))] + [hang-p-tan (/ (+ (sin a) (sin b)) (+ (cos a) (cos b))) + (tan (/ (+ a b) 2))] + [hang-m-tan (/ (- (sin a) (sin b)) (+ (cos a) (cos b))) + (tan (/ (- a b) 2))]) + +(define-ruleset trig-reduce-fp-sound (trigonometry simplify fp-safe) + [sin-0 (sin 0) 0] + [cos-0 (cos 0) 1] + [tan-0 (tan 0) 0]) + +(define-ruleset trig-reduce-fp-sound-nan (trigonometry simplify fp-safe-nan) + [sin-neg (sin (- x)) (- (sin x))] + [cos-neg (cos (- x)) (cos x)] + [tan-neg (tan (- x)) (- (tan x))]) (define-ruleset trig-expand (trigonometry) - [sqr-sin (sqr (sin x)) (- 1 (sqr (cos x)))] - [sqr-cos (sqr (cos x)) (- 1 (sqr (sin x)))] - [sin-sum (sin (+ x y)) (+ (* (sin x) (cos y)) (* (cos x) (sin y)))] - [cos-sum (cos (+ x y)) (- (* (cos x) (cos y)) (* (sin x) (sin y)))] - [tan-sum (tan (+ x y)) (/ (+ (tan x) (tan y)) (- 1 (* (tan x) (tan y))))] - [sin-diff (sin (- x y)) (- (* (sin x) (cos y)) (* (cos x) (sin y)))] - [cos-diff (cos (- x y)) (+ (* (cos x) (cos y)) (* (sin x) (sin y)))] - [sin-2 (sin (* 2 x)) (* 2 (* (sin x) (cos x)))] - [sin-3 (sin (* 3 x)) (- (* 3 (sin x)) (* 4 (cube (sin x))))] - [2-sin (* 2 (* (sin x) (cos x))) (sin (* 2 x))] - [3-sin (- (* 3 (sin x)) (* 4 (cube (sin x)))) (sin (* 3 x))] - [cos-2 (cos (* 2 x)) (- (sqr (cos x)) (sqr (sin x)))] - [cos-3 (cos (* 3 x)) (- (* 4 (cube (cos x))) (* 3 (cos x)))] - [2-cos (- (sqr (cos x)) (sqr (sin x))) (cos (* 2 x))] - [3-cos (- (* 4 (cube (cos x))) (* 3 (cos x))) (cos (* 3 x))] - [tan-2 (tan (* 2 x)) (/ (* 2 (tan x)) (- 1 (sqr (tan x))))] - [2-tan (/ (* 2 (tan x)) (- 1 (sqr (tan x)))) (tan (* 2 x))] - [sqr-sin (sqr (sin x)) (- 1/2 (* 1/2 (cos (* 2 x))))] - [sqr-cos (sqr (cos x)) (+ 1/2 (* 1/2 (cos (* 2 x))))] - [diff-sin (- (sin x) (sin y)) (* 2 (* (sin (/ (- x y) 2)) (cos (/ (+ x y) 2))))] - [diff-cos (- (cos x) (cos y)) (* -2 (* (sin (/ (- x y) 2)) (sin (/ (+ x y) 2))))] - [sum-sin (+ (sin x) (sin y)) (* 2 (* (sin (/ (+ x y) 2)) (cos (/ (- x y) 2))))] - [sum-cos (+ (cos x) (cos y)) (* 2 (* (cos (/ (+ x y) 2)) (cos (/ (- x y) 2))))] - [cos-mult (* (cos x) (cos y)) (/ (+ (cos (+ x y)) (cos (- x y))) 2)] - [sin-mult (* (sin x) (sin y)) (/ (- (cos (- x y)) (cos (+ x y))) 2)] - [sin-cos-mult (* (sin x) (cos y)) (/ (+ (sin (- x y)) (sin (+ x y))) 2)] - [diff-atan (- (atan x) (atan y)) (atan2 (- x y) (+ 1 (* x y)))] - [sum-atan (+ (atan x) (atan y)) (atan2 (+ x y) (- 1 (* x y)))] - [tan-quot (tan x) (/ (sin x) (cos x))] - [quot-tan (/ (sin x) (cos x)) (tan x)]) + [sin-sum (sin (+ x y)) (+ (* (sin x) (cos y)) (* (cos x) (sin y)))] + [cos-sum (cos (+ x y)) (- (* (cos x) (cos y)) (* (sin x) (sin y)))] + [tan-sum (tan (+ x y)) (/ (+ (tan x) (tan y)) (- 1 (* (tan x) (tan y))))] + [sin-diff (sin (- x y)) (- (* (sin x) (cos y)) (* (cos x) (sin y)))] + [cos-diff (cos (- x y)) (+ (* (cos x) (cos y)) (* (sin x) (sin y)))] + [sin-2 (sin (* 2 x)) + (* 2 (* (sin x) (cos x)))] + [sin-3 (sin (* 3 x)) + (- (* 3 (sin x)) (* 4 (pow (sin x) 3)))] + [2-sin (* 2 (* (sin x) (cos x))) + (sin (* 2 x))] + [3-sin (- (* 3 (sin x)) (* 4 (pow (sin x) 3))) + (sin (* 3 x))] + [cos-2 (cos (* 2 x)) + (- (* (cos x) (cos x)) (* (sin x) (sin x)))] + [cos-3 (cos (* 3 x)) + (- (* 4 (pow (cos x) 3)) (* 3 (cos x)))] + [2-cos (- (* (cos x) (cos x)) (* (sin x) (sin x))) + (cos (* 2 x))] + [3-cos (- (* 4 (pow (cos x) 3)) (* 3 (cos x))) + (cos (* 3 x))] + [tan-2 (tan (* 2 x)) (/ (* 2 (tan x)) (- 1 (* (tan x) (tan x))))] + [2-tan (/ (* 2 (tan x)) (- 1 (* (tan x) (tan x)))) (tan (* 2 x))] + [sqr-sin (* (sin x) (sin x)) (- 1/2 (* 1/2 (cos (* 2 x))))] + [sqr-cos (* (cos x) (cos x)) (+ 1/2 (* 1/2 (cos (* 2 x))))] + [diff-sin (- (sin x) (sin y)) (* 2 (* (sin (/ (- x y) 2)) (cos (/ (+ x y) 2))))] + [diff-cos (- (cos x) (cos y)) (* -2 (* (sin (/ (- x y) 2)) (sin (/ (+ x y) 2))))] + [sum-sin (+ (sin x) (sin y)) (* 2 (* (sin (/ (+ x y) 2)) (cos (/ (- x y) 2))))] + [sum-cos (+ (cos x) (cos y)) (* 2 (* (cos (/ (+ x y) 2)) (cos (/ (- x y) 2))))] + [cos-mult (* (cos x) (cos y)) (/ (+ (cos (+ x y)) (cos (- x y))) 2)] + [sin-mult (* (sin x) (sin y)) (/ (- (cos (- x y)) (cos (+ x y))) 2)] + [sin-cos-mult (* (sin x) (cos y)) (/ (+ (sin (- x y)) (sin (+ x y))) 2)] + [diff-atan (- (atan x) (atan y)) (atan2 (- x y) (+ 1 (* x y)))] + [sum-atan (+ (atan x) (atan y)) (atan2 (+ x y) (- 1 (* x y)))] + [tan-quot (tan x) (/ (sin x) (cos x))] + [quot-tan (/ (sin x) (cos x)) (tan x)] + [tan-hang-p (tan (/ (+ a b) 2)) + (/ (+ (sin a) (sin b)) (+ (cos a) (cos b)))] + [tan-hang-m (tan (/ (- a b) 2)) + (/ (- (sin a) (sin b)) (+ (cos a) (cos b)))]) + +(define-ruleset trig-expand-fp-safe (trignometry fp-safe) + [sqr-sin (* (sin x) (sin x)) (- 1 (* (cos x) (cos x)))] + [sqr-cos (* (cos x) (cos x)) (- 1 (* (sin x) (sin x)))]) (define-ruleset atrig-expand (trigonometry) [sin-asin (sin (asin x)) x] - [cos-asin (cos (asin x)) (sqrt (- 1 (sqr x)))] - [tan-asin (tan (asin x)) (/ x (sqrt (- 1 (sqr x))))] - [sin-acos (sin (acos x)) (sqrt (- 1 (sqr x)))] + [cos-asin (cos (asin x)) (sqrt (- 1 (* x x)))] + [tan-asin (tan (asin x)) (/ x (sqrt (- 1 (* x x))))] + [sin-acos (sin (acos x)) (sqrt (- 1 (* x x)))] [cos-acos (cos (acos x)) x] - [tan-acos (tan (acos x)) (/ (sqrt (- 1 (sqr x))) x)] - [sin-atan (sin (atan x)) (/ x (sqrt (+ 1 (sqr x))))] - [cos-atan (cos (atan x)) (/ 1 (sqrt (+ 1 (sqr x))))] + [tan-acos (tan (acos x)) (/ (sqrt (- 1 (* x x))) x)] + [sin-atan (sin (atan x)) (/ x (sqrt (+ 1 (* x x))))] + [cos-atan (cos (atan x)) (/ 1 (sqrt (+ 1 (* x x))))] [tan-atan (tan (atan x)) x] [asin-acos (asin x) (- (/ PI 2) (acos x))] [acos-asin (acos x) (- (/ PI 2) (asin x))] @@ -347,7 +411,7 @@ [tanh-def (tanh x) (/ (- (exp x) (exp (- x))) (+ (exp x) (exp (- x))))] [tanh-def (tanh x) (/ (- (exp (* 2 x)) 1) (+ (exp (* 2 x)) 1))] [tanh-def (tanh x) (/ (- 1 (exp (* -2 x))) (+ 1 (exp (* -2 x))))] - [sinh-cosh (- (sqr (cosh x)) (sqr (sinh x))) 1] + [sinh-cosh (- (* (cosh x) (cosh x)) (* (sinh x) (sinh x))) 1] [sinh-+-cosh (+ (cosh x) (sinh x)) (exp x)] [sinh---cosh (- (cosh x) (sinh x)) (exp (- x))]) @@ -355,20 +419,16 @@ [sinh-undef (/ (- (exp x) (exp (- x))) 2) (sinh x)] [cosh-undef (/ (+ (exp x) (exp (- x))) 2) (cosh x)] [tanh-undef (/ (- (exp x) (exp (- x))) (+ (exp x) (exp (- x)))) (tanh x)] - [sinh-neg (sinh (- x)) (- (sinh x))] - [sinh-0 (sinh 0) 0] - [cosh-neg (cosh (- x)) (cosh x)] - [cosh-0 (cosh 0) 1] [cosh-sum (cosh (+ x y)) (+ (* (cosh x) (cosh y)) (* (sinh x) (sinh y)))] [cosh-diff (cosh (- x y)) (- (* (cosh x) (cosh y)) (* (sinh x) (sinh y)))] - [cosh-2 (cosh (* 2 x)) (+ (sqr (sinh x)) (sqr (cosh x)))] + [cosh-2 (cosh (* 2 x)) (+ (* (sinh x) (sinh x)) (* (cosh x) (cosh x)))] [cosh-1/2 (cosh (/ x 2)) (sqrt (/ (+ (cosh x) 1) 2))] [sinh-sum (sinh (+ x y)) (+ (* (sinh x) (cosh y)) (* (cosh x) (sinh y)))] [sinh-diff (sinh (- x y)) (- (* (sinh x) (cosh y)) (* (cosh x) (sinh y)))] [sinh-2 (sinh (* 2 x)) (* 2 (* (sinh x) (cosh x)))] [sinh-1/2 (sinh (/ x 2)) (/ (sinh x) (sqrt (* 2 (+ (cosh x) 1))))] [tanh-sum (tanh (+ x y)) (/ (+ (tanh x) (tanh y)) (+ 1 (* (tanh x) (tanh y))))] - [tanh-2 (tanh (* 2 x)) (/ (* 2 (tanh x)) (+ 1 (sqr (tanh x))))] + [tanh-2 (tanh (* 2 x)) (/ (* 2 (tanh x)) (+ 1 (* (tanh x) (tanh x))))] [tanh-1/2 (tanh (/ x 2)) (/ (sinh x) (+ (cosh x) 1))] [tanh-1/2* (tanh (/ x 2)) (/ (- (cosh x) 1) (sinh x))] [sum-sinh (+ (sinh x) (sinh y)) (* 2 (* (sinh (/ (+ x y) 2)) (cosh (/ (- x y) 2))))] @@ -376,20 +436,26 @@ [diff-sinh (- (sinh x) (sinh y)) (* 2 (* (cosh (/ (+ x y) 2)) (sinh (/ (- x y) 2))))] [diff-cosh (- (cosh x) (cosh y)) (* 2 (* (sinh (/ (+ x y) 2)) (sinh (/ (- x y) 2))))]) +(define-ruleset htrig-expand-fp-safe (hyperbolic fp-safe) + [sinh-neg (sinh (- x)) (- (sinh x))] + [sinh-0 (sinh 0) 0] + [cosh-neg (cosh (- x)) (cosh x)] + [cosh-0 (cosh 0) 1]) + (define-ruleset ahtrig-expand (hyperbolic) - [asinh-def (asinh x) (log (+ x (sqrt (+ (sqr x) 1))))] - [acosh-def (acosh x) (log (+ x (sqrt (- (sqr x) 1))))] + [asinh-def (asinh x) (log (+ x (sqrt (+ (* x x) 1))))] + [acosh-def (acosh x) (log (+ x (sqrt (- (* x x) 1))))] [atanh-def (atanh x) (/ (log (/ (+ 1 x) (- 1 x))) 2)] - [acosh-2 (acosh (- (* 2 (sqr x)) 1)) (* 2 (acosh x))] - [asinh-2 (acosh (+ (* 2 (sqr x)) 1)) (* 2 (asinh x))] + [acosh-2 (acosh (- (* 2 (* x x)) 1)) (* 2 (acosh x))] + [asinh-2 (acosh (+ (* 2 (* x x)) 1)) (* 2 (asinh x))] [sinh-asinh (sinh (asinh x)) x] - [sinh-acosh (sinh (acosh x)) (sqrt (- (sqr x) 1))] - [sinh-atanh (sinh (atanh x)) (/ x (sqrt (- 1 (sqr x))))] - [cosh-asinh (cosh (asinh x)) (sqrt (+ (sqr x) 1))] + [sinh-acosh (sinh (acosh x)) (sqrt (- (* x x) 1))] + [sinh-atanh (sinh (atanh x)) (/ x (sqrt (- 1 (* x x))))] + [cosh-asinh (cosh (asinh x)) (sqrt (+ (* x x) 1))] [cosh-acosh (cosh (acosh x)) x] - [cosh-atanh (cosh (atanh x)) (/ 1 (sqrt (- 1 (sqr x))))] - [tanh-asinh (tanh (asinh x)) (/ x (sqrt (+ 1 (sqr x))))] - [tanh-acosh (tanh (acosh x)) (/ (sqrt (- (sqr x) 1)) x)] + [cosh-atanh (cosh (atanh x)) (/ 1 (sqrt (- 1 (* x x))))] + [tanh-asinh (tanh (asinh x)) (/ x (sqrt (+ 1 (* x x))))] + [tanh-acosh (tanh (acosh x)) (/ (sqrt (- (* x x) 1)) x)] [tanh-atanh (tanh (atanh x)) x]) ; Specialized numerical functions @@ -398,34 +464,129 @@ [log1p-def (log (+ 1 x)) (log1p x)] [log1p-expm1 (log1p (expm1 x)) x] [expm1-log1p (expm1 (log1p x)) x] - [hypot-def (sqrt (+ (sqr x) (sqr y))) (hypot x y)] - [hypot-1-def (sqrt (+ 1 (sqr y))) (hypot 1 y)] + [hypot-def (sqrt (+ (* x x) (* y y))) (hypot x y)] + [hypot-1-def (sqrt (+ 1 (* y y))) (hypot 1 y)] [fma-def (+ (* x y) z) (fma x y z)] - [fma-neg (- (* x y) z) (fma x y (- z))]) + [fma-neg (- (* x y) z) (fma x y (- z))] + [fma-udef (fma x y z) (+ (* x y) z)]) (define-ruleset special-numerical-expand (numerics) [expm1-udef (expm1 x) (- (exp x) 1)] [log1p-udef (log1p x) (log (+ 1 x))] [log1p-expm1-u x (log1p (expm1 x))] [expm1-log1p-u x (expm1 (log1p x))] - [hypot-udef (hypot x y) (sqrt (+ (sqr x) (sqr y)))] - [fma-udef (fma x y z) (+ (* x y) z)]) + [hypot-udef (hypot x y) (sqrt (+ (* x x) (* y y)))]) + +(define-ruleset numerics-papers (numerics) + ; "Further Analysis of Kahan's Algorithm for + ; the Accurate Computation of 2x2 Determinants" + ; Jeannerod et al., Mathematics of Computation, 2013 + ; + ; a * b - c * d ===> fma(a, b, -(d * c)) + fma(-d, c, d * c) + [prod-diff (- (* a b) (* c d)) + (+ (fma a b (- (* d c))) + (fma (- d) c (* d c)))]) + +(define-ruleset bool-reduce (bools simplify fp-safe) + #:type ([a bool] [b bool]) + [not-true (not TRUE) FALSE] + [not-false (not FALSE) TRUE] + [not-not (not (not a)) a] + [not-and (not (and a b)) (or (not a) (not b))] + [not-or (not (or a b)) (and (not a) (not b))] + [and-true-l (and TRUE a) a] + [and-true-r (and a TRUE) a] + [and-false-l (and FALSE a) FALSE] + [and-false-r (and a FALSE) FALSE] + [and-same (and a a) a] + [or-true-l (or TRUE a) TRUE] + [or-true-r (or a TRUE) TRUE] + [or-false-l (or FALSE a) a] + [or-false-r (or a FALSE) a] + [or-same (or a a) a]) + +(define-ruleset compare-reduce (bools simplify fp-safe-nan) + [lt-same (< x x) FALSE] + [gt-same (> x x) FALSE] + [lte-same (<= x x) TRUE] + [gte-same (>= x x) TRUE] + [not-lt (not (< x y)) (>= x y)] + [not-gt (not (> x y)) (<= x y)] + [not-lte (not (<= x y)) (> x y)] + [not-gte (not (>= x y)) (< x y)]) + +(define-ruleset branch-reduce (branches simplify fp-safe) + #:type ([a bool] [b bool]) + [if-true (if TRUE x y) x] + [if-false (if FALSE x y) y] + [if-same (if a x x) x] + [if-not (if (not a) x y) (if a y x)] + [if-if-or (if a x (if b x y)) (if (or a b) x y)] + [if-if-or-not (if a x (if b y x)) (if (or a (not b)) x y)] + [if-if-and (if a (if b x y) y) (if (and a b) x y)] + [if-if-and-not (if a (if b y x) y) (if (and a (not b)) x y)]) + +(define-ruleset complex-number-basics (complex simplify) + [real-part (re (complex x y)) x] + [imag-part (im (complex x y)) y] + [complex-add-def (+ (complex a b) (complex c d)) (complex (+ a c) (+ b d))] + [complex-def-add (complex (+ a c) (+ b d)) (+ (complex a b) (complex c d))] + [complex-sub-def (- (complex a b) (complex c d)) (complex (- a c) (- b d))] + [complex-def-sub (complex (- a c) (- b d)) (- (complex a b) (complex c d))] + [complex-neg-def (- (complex a b)) (complex (- a) (- b))] + [complex-def-neg (complex (- a) (- b)) (- (complex a b))] + [complex-mul-def (* (complex a b) (complex c d)) (complex (- (* a c) (* b d)) (+ (* a d) (* b c)))] + [complex-div-def (/ (complex a b) (complex c d)) (complex (/ (+ (* a c) (* b d)) (+ (* c c) (* d d))) (/ (- (* b c) (* a d)) (+ (* c c) (* d d))))] + [complex-conj-def (conj (complex a b)) (complex a (- b))] + ) + +(define-ruleset erf-rules (special simplify) + [erf-odd (erf (- x)) (- (erf x))] + [erf-erfc (erfc x) (- 1 (erf x))] + [erfc-erf (- 1 (erf x)) (erfc x)]) + +(define (rule-valid-at-type? rule type) + (match type + ['complex (set-member? (for/set ([r (*complex-rules*)]) (values (rule-name r))) (rule-name rule))] + ['real #t] + [_ #f])) + +(module+ test + (for ([r (*complex-rules*)]) + (check-equal? #t (rule-valid-at-type? r 'complex))) + (for ([r (*rules*)]) + (check-equal? #t (rule-valid-at-type? r 'real)))) + +(define (*complex-rules*) + (for/append ([rec (*rulesets*)]) + (match-define (list rules groups _) rec) + (if (set-member? groups 'complex) rules '()))) (define (*rules*) - (for/append ([(rules groups) (in-dict (*rulesets*))]) - (if (ormap (λ (x) ((flag 'rules x) #t #f)) groups) rules '()))) + (for/append ([rec (*rulesets*)]) + (match-define (list rules groups _) rec) + (if (ormap (curry flag-set? 'rules) groups) rules '()))) (define (*simplify-rules*) - (for/append ([(rules groups) (in-dict (*rulesets*))]) - (if (and (ormap (λ (x) ((flag 'rules x) #t #f)) groups) - (memq 'simplify groups)) + (for/append ([rec (*rulesets*)]) + (match-define (list rules groups _) rec) + (if (and (ormap (curry flag-set? 'rules) groups) + (set-member? groups 'simplify)) + rules + '()))) + +(define (*fp-safe-simplify-rules*) + (for/append ([rec (*rulesets*)]) + (match-define (list rules groups _) rec) + (if (and (ormap (curry flag-set? 'rules) groups) + (set-member? groups 'fp-safe) + (set-member? groups 'simplify)) rules '()))) (module+ test (require rackunit math/bigfloat) - (require "../programs.rkt" "../float.rkt" "distributions.rkt") - (define sampler (eval-sampler 'default)) + (require "../programs.rkt" "../float.rkt") (define num-test-points 2000) (define *conditions* @@ -439,34 +600,46 @@ [tanh-acosh . (> (fabs x) 1)])) (define *skip-tests* - ;; All these tests fail due to underflow to 0 and are irrelevant - '(exp-prod pow-unpow pow-pow pow-exp - asinh-2 tanh-1/2* sinh-cosh)) - - (for ([test-rule (*rules*)] #:when (not (set-member? *skip-tests* (rule-name test-rule)))) + (append + ;; All these tests fail due to underflow to 0 and are irrelevant + '(exp-prod pow-unpow pow-pow pow-exp + asinh-2 tanh-1/2* sinh-cosh + hang-p0-tan hang-m0-tan))) + + (for* ([test-ruleset (*rulesets*)] + [test-rule (first test-ruleset)] + #:unless (set-member? *skip-tests* (rule-name test-rule))) (parameterize ([bf-precision 2000]) (with-check-info (['rule test-rule]) - (with-handlers ([exn:fail? (λ (e) (fail (exn-message e)))]) + (with-handlers ([exn:fail? (λ (e) + ((error-display-handler) + (exn-message e) e) + (fail (exn-message e)))]) (match-define (rule name p1 p2) test-rule) ;; Not using the normal prepare-points machinery for speed. (define fv (free-variables p1)) (define valid-point? (if (dict-has-key? *conditions* name) - (eval-prog `(λ ,fv ,(dict-ref *conditions* name)) mode:bf) + (eval-prog `(λ ,fv ,(dict-ref *conditions* name)) 'bf) (const true))) - (define (make-point) (for/list ([v fv]) (sampler))) + (define (make-point) + (for/list ([v fv]) + (match (dict-ref (third test-ruleset) v 'real) + ['real (sample-double)] + ['bool (if (< (random) .5) false true)] + ['complex (make-rectangular (sample-double) (sample-double))]))) (define point-sequence (sequence-filter valid-point? (in-producer make-point))) (define points (for/list ([n (in-range num-test-points)] [pt point-sequence]) pt)) - (define prog1 (eval-prog `(λ ,fv ,p1) mode:bf)) - (define prog2 (eval-prog `(λ ,fv ,p2) mode:bf)) + (define prog1 (eval-prog `(λ ,fv ,p1) 'bf)) + (define prog2 (eval-prog `(λ ,fv ,p2) 'bf)) (with-handlers ([exn:fail:contract? (λ (e) (eprintf "~a: ~a\n" name (exn-message e)))]) (define ex1 (map prog1 points)) (define ex2 (map prog2 points)) (define errs (for/list ([v1 ex1] [v2 ex2]) ;; Ignore points not in the input or output domain - (if (and (ordinary-float? v1) (ordinary-float? v2)) + (if (and (ordinary-value? v1) (ordinary-value? v2)) (ulps->bits (+ (abs (ulp-difference v1 v2)) 1)) #f))) (when (< (length (filter identity errs)) 100) @@ -479,3 +652,38 @@ ['max-input (third max-error)] ['max-output (fourth max-error)]) (check-pred (curryr <= 1) score)))))))) + +(module+ test + (require rackunit math/bigfloat) + (require "../programs.rkt" "../float.rkt") + + (for* ([test-ruleset (*rulesets*)] + [test-rule (first test-ruleset)] + #:when (set-member? (*fp-safe-simplify-rules*) test-rule)) + (with-check-info (['rule test-rule]) + (with-handlers ([exn:fail? (λ (e) (fail (exn-message e)))]) + (define num-test-points 2000) + (match-define (rule name p1 p2) test-rule) + (define fv (free-variables p1)) + (define (make-point) + (for/list ([v fv]) + (match (dict-ref (third test-ruleset) v 'real) + ['real (sample-double)] + ['bool (if (< (random) .5) false true)] + ['complex (make-rectangular (sample-double) (sample-double))]))) + (define point-sequence (in-producer make-point)) + (define points (for/list ([n (in-range num-test-points)] [pt point-sequence]) pt)) + (define prog1 (eval-prog `(λ ,fv ,p1) 'fl)) + (define prog2 (eval-prog `(λ ,fv, p2) 'fl)) + (with-handlers ([exn:fail:contract? (λ (e) (eprintf "~a: ~a\n" name (exn-message e)))]) + (define ex1 (map prog1 points)) + (define ex2 (map prog2 points)) + (define err + (for/first ([pt points] [v1 ex1] [v2 ex2] + #:unless (equal? v1 v2)) + (list pt v1 v2))) + (when err + (match-define (list pt v1 v2) err) + (with-check-info (['point (map list fv pt)] ['input-value v1] ['output-value v2]) + (check-false err)))))))) +; diff --git a/src/syntax/syntax.rkt b/src/syntax/syntax.rkt index c88bc42a5..df0e6a9f1 100644 --- a/src/syntax/syntax.rkt +++ b/src/syntax/syntax.rkt @@ -1,130 +1,522 @@ #lang racket (require math/flonum) +(require math/base) (require math/bigfloat) +(require math/special-functions) (require "../common.rkt") - -(provide *operations* predicates constants constant? variable? - mode:bf mode:fl mode:args mode:cost ->bf ->flonum - program-body program-variables - real-op->bigfloat-op - real-op->float-op) - -; Programs are just lambda expressions -(define program-body caddr) -(define program-variables cadr) - -; Functions and constants used in our language -(define nan ((flag 'precision 'double) +nan.0 +nan.f)) -; TODO add infinity - -; Use C ffi to get numerical ops from libm -(require ffi/unsafe ffi/unsafe/define) -(define-ffi-definer define-libm #f - #:default-make-fail make-not-available) - -(define-syntax-rule (libm_op1 id_fl id_d id_f) - (begin - (define-libm id_d (_fun _double -> _double)) - (define-libm id_f (_fun _float -> _float )) - (define (id_fl x) - ((flag 'precision 'double) - (id_d (real->double-flonum x)) - (id_f (real->single-flonum x)))))) - -(define-syntax-rule (libm_op2 id_fl id_d id_f) - (begin - (define-libm id_d (_fun _double _double -> _double)) - (define-libm id_f (_fun _float _float -> _float )) - (define (id_fl x y) - ((flag 'precision 'double) - (id_d (real->double-flonum x) (real->double-flonum y)) - (id_f (real->single-flonum x) (real->single-flonum y)))))) - -(define-syntax-rule (libm_op3 id_fl id_d id_f) - (begin - (define-libm id_d (_fun _double _double _double -> _double)) - (define-libm id_f (_fun _float _float _float -> _float )) - (define (id_fl x y z) - ((flag 'precision 'double) - (id_d (real->double-flonum x) (real->double-flonum y) (real->double-flonum z)) - (id_f (real->single-flonum x) (real->single-flonum y) (real->single-flonum z)))))) - -; Supported ops from libm (https://goo.gl/auVJi5) -(libm_op1 _flacos acos acosf) -(libm_op1 _flacosh acosh acoshf) -(libm_op1 _flasin asin asinf) -(libm_op1 _flasinh asinh asinhf) -(libm_op1 _flatan atan atanf) -(libm_op2 _flatan2 atan2 atan2f) -(libm_op1 _flatanh atanh atanhf) -(libm_op1 _flcbrt cbrt cbrtf) -(libm_op1 _flceil ceil ceilf) -(libm_op2 _flcopysign copysign copysignf) -(libm_op1 _flcos cos cosf) -(libm_op1 _flcosh cosh coshf) -(libm_op1 _flerf erf erff) -(libm_op1 _flerfc erfc erfcf) -(libm_op1 _flexp exp expf) -(libm_op1 _flexp2 exp2 exp2f) -(libm_op1 _flexpm1 expm1 expm1f) -(libm_op1 _flfabs fabs fabsf) -(libm_op2 _flfdim fdim fdimf) -(libm_op1 _flfloor floor floorf) -(libm_op3 _flfma fma fmaf) -(libm_op2 _flfmax fmax fmaxf) -(libm_op2 _flfmin fmin fminf) -(libm_op2 _flfmod fmod fmodf) -(libm_op2 _flhypot hypot hypotf) -(libm_op1 _flj0 j0 j0f) -(libm_op1 _flj1 j1 j1f) -(libm_op1 _fllgamma lgamma lgammaf) -(libm_op1 _fllog log logf) -(libm_op1 _fllog10 log10 log10f) -(libm_op1 _fllog1p log1p log1pf) -(libm_op1 _fllog2 log2 log2f) -(libm_op1 _fllogb logb logbf) -(libm_op2 _flpow pow powf) -(libm_op2 _flremainder remainder remainderf) -(libm_op1 _flrint rint rintf) -(libm_op1 _flround round roundf) -(libm_op1 _flsin sin sinf) -(libm_op1 _flsinh sinh sinhf) -(libm_op1 _flsqrt sqrt sqrtf) -(libm_op1 _fltan tan tanf) -(libm_op1 _fltanh tanh tanhf) -(libm_op1 _fltgamma tgamma tgammaf) -(libm_op1 _fltrunc trunc truncf) -(libm_op1 _fly0 y0 y0f) -(libm_op1 _fly1 y1 y1f) - -(define (_flcube x) - (* x (* x x))) - -(define (_flsqr x) - (* x x)) +(require "../float.rkt") +(require "../bigcomplex.rkt") + +(provide constant? variable? operator? operator-info constant-info prune-operators! + *unknown-d-ops* *unknown-f-ops* *loaded-ops*) + +(define *unknown-d-ops* (make-parameter '())) +(define *unknown-f-ops* (make-parameter '())) + +(define *loaded-ops* (make-parameter '())) + +(define (type? x) (or (equal? x 'real) (equal? x 'bool) (equal? x 'complex))) + +;; Constants's values are defined as functions to allow them to +;; depend on (bf-precision) and (flag 'precision 'double). + +(define-table constants + [type type?] + [bf (->* () (or/c bigfloat? boolean?))] + [fl (->* () (or/c flonum? boolean?))] + [->c/double string?] + [->c/mpfr (->* (string?) string?)] + [->tex string?]) + +(define (constant-info constant field) (table-ref constants constant field)) + +(define-syntax-rule (define-constant constant ctype [key value] ...) + (table-set! constants 'constant + (make-hash (list (cons 'type 'ctype) (cons 'key value) ...)))) + +(define-constant PI real + [bf (λ () pi.bf)] + [fl (λ () pi)] + [->c/double "atan2(1.0, 0.0)"] + [->c/mpfr (curry format "mpfr_const_pi(~a, MPFR_RNDN)")] + [->tex "\\pi"]) + +(define-constant E real + [bf (λ () (bfexp 1.bf))] + [fl (λ () (exp 1.0))] + [->c/double "exp(1.0)"] + [->c/mpfr (λ (x) (format "mpfr_set_si(~a, 1, MPFR_RNDN), mpfr_const_exp(~a, ~a, MPFR_RNDN)" x x x))] + [->tex "e"]) + +(define-constant TRUE bool + [bf (const true)] + [fl (const true)] + [->c/double "1"] + [->c/mpfr (curry format "mpfr_set_si(~a, 1, MPFR_RNDN)")] + [->tex "\\top"]) + +(define-constant FALSE bool + [bf (const false)] + [fl (const false)] + [->c/double "0"] + [->c/mpfr (curry format "mpfr_set_si(~a, 0, MPFR_RNDN)")] + [->tex "\\perp"]) + +;; TODO: The contracts for operators are tricky because the number of arguments is unknown +;; There's no easy way to write such a contract in Racket, so I only constrain the output type. +(define (unconstrained-argument-number-> from/c to/c) + (unconstrained-domain-> to/c)) + +;; TODO: the costs below seem likely to be incorrect, and also do we still need them? +(define-table operators + [args (listof (or/c '* natural-number/c))] + [bf (unconstrained-argument-number-> (or/c bigfloat? boolean? bigcomplex?) (or/c bigfloat? boolean? bigcomplex?))] + [fl (unconstrained-argument-number-> (or/c flonum? boolean? complex?) (or/c flonum? boolean? complex?))] + [nonffi (unconstrained-argument-number-> (or/c real? boolean? complex?) (or/c real? boolean? complex?))] + [cost natural-number/c] + [type (hash/c (or/c '* natural-number/c) (listof (list/c (or/c (listof type?) (list/c '* type?)) type?)))] + [->c/double (unconstrained-argument-number-> string? string?)] + [->c/mpfr (unconstrained-argument-number-> string? string?)] + [->tex (unconstrained-argument-number-> string? string?)]) + +(define (operator-info operator field) (table-ref operators operator field)) + +(define (operator-remove! operator) + (table-remove! operators operator) + (*loaded-ops* (set-remove (*loaded-ops*) operator))) + +(define (prune-operators!) + (unless (flag-set? 'precision 'fallback) + (for ([op (if (flag-set? 'precision 'double) (*unknown-d-ops*) (*unknown-f-ops*))]) + (operator-remove! op))) + (unless (flag-set? 'fn 'cbrt) (operator-remove! 'cbrt))) + +(define-syntax-rule (define-operator (operator atypes ...) rtype [key value] ...) + (let ([type (hash (length '(atypes ...)) (list (list '(atypes ...) 'rtype)))] + [args (list (length '(atypes ...)))]) + (*loaded-ops* (cons 'operator (*loaded-ops*))) + (table-set! operators 'operator + (make-hash (list (cons 'type type) (cons 'args args) (cons 'key value) ...))))) + +(define (default-nonffi . args) + (raise + (make-exn:fail:unsupported + (format "couldn't find ~a and no default implementation defined" 'operator) + (current-continuation-marks)))) + +(define-operator (+ real real) real + [args '(2)] [type (hash 2 '(((real real) real) ((complex complex) complex)))] + [fl +] [bf exact+] [cost 40] + [->c/double (curry format "~a + ~a")] + [->c/mpfr (curry format "mpfr_add(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "~a + ~a")] + [nonffi +]) + +(define-operator (- real [real]) real + ;; Override the normal argument handling because - can be unary + [args '(1 2)] [type (hash 1 '(((real) real) ((complex) complex)) 2 '(((real real) real) ((complex complex) complex)))] + [fl -] [bf exact-] [cost 40] + [->c/double (λ (x [y #f]) (if y (format "~a - ~a" x y) (format "-~a" x)))] + [->c/mpfr (λ (out x [y #f]) (if y (format "mpfr_sub(~a, ~a, ~a, MPFR_RNDN)" out x y) (format "mpfr_neg(~a, ~a, MPFR_RNDN)" out x)))] + [->tex (λ (x [y #f]) (if y (format "~a - ~a" x y) (format "-~a" x)))] + [nonffi -]) + +(define-operator (* real real) real + [args '(2)] [type (hash 2 '(((real real) real) ((complex complex) complex)))] + [fl *] [bf exact*] [cost 40] + [->c/double (curry format "~a * ~a")] + [->c/mpfr (curry format "mpfr_mul(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "~a \\cdot ~a")] + [nonffi *]) + +(define-operator (/ real real) real + [args '(2)] [type (hash 2 '(((real real) real) ((complex complex) complex)))] + [fl /] [bf exact/] [cost 40] + [->c/double (curry format "~a / ~a")] + [->c/mpfr (curry format "mpfr_div(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\frac{~a}{~a}")] + [nonffi /]) + +(require ffi/unsafe) +(define-syntax (define-operator/libm stx) + (syntax-case stx (real libm) + [(_ (operator real ...) real [libm id_d id_f] [key value] ...) + (let ([num-args (length (cdr (syntax-e (cadr (syntax-e stx)))))]) + #`(begin + (define double-proc (get-ffi-obj 'id_d #f (_fun #,@(build-list num-args (λ (_) #'_double)) -> _double) + (lambda () + (*unknown-d-ops* (cons 'operator (*unknown-d-ops*))) + (λ args (apply (operator-info 'operator 'nonffi) args))))) + (define float-proc (get-ffi-obj 'id_f #f (_fun #,@(build-list num-args (λ (_) #'_float)) -> _float) + (lambda () + (*unknown-f-ops* (cons 'operator (*unknown-f-ops*))) + (λ args (apply (operator-info 'operator 'nonffi) args))))) + (define-operator (operator #,@(build-list num-args (λ (_) #'real))) real + [fl (λ args (apply (if (flag-set? 'precision 'double) double-proc float-proc) args))] + [key value] ...)))])) + +(define-operator/libm (acos real) real + [libm acos acosf] [bf bfacos] [cost 90] + [->c/double (curry format "acos(~a)")] + [->c/mpfr (curry format "mpfr_acos(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\cos^{-1} ~a")] + [nonffi acos]) + +(define-operator/libm (acosh real) real + [libm acosh acoshf] [bf bfacosh] [cost 55] + [->c/double (curry format "acosh(~a)")] + [->c/mpfr (curry format "mpfr_acosh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\cosh^{-1} ~a")] + [nonffi acosh]) + +(define-operator/libm (asin real) real + [libm asin asinf] [bf bfasin] [cost 105] + [->c/double (curry format "asin(~a)")] + [->c/mpfr (curry format "mpfr_asin(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sin^{-1} ~a")] + [nonffi asin]) + +(define-operator/libm (asinh real) real + [libm asinh asinhf] [bf bfasinh] [cost 55] + [->c/double (curry format "asinh(~a)")] + [->c/mpfr (curry format "mpfr_asinh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sinh^{-1} ~a")] + [nonffi asinh]) + +(define-operator/libm (atan real) real + [libm atan atanf] [bf bfatan] [cost 105] + [->c/double (curry format "atan(~a)")] + [->c/mpfr (curry format "mpfr_atan(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\tan^{-1} ~a")] + [nonffi atan]) + +(define-operator/libm (atan2 real real) real + [libm atan2 atan2f] [bf bfatan2] [cost 140] + [->c/double (curry format "atan2(~a, ~a)")] + [->c/mpfr (curry format "mpfr_atan2(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\tan^{-1}_* \\frac{~a}{~a}")] + [nonffi atan]) + +(define-operator/libm (atanh real) real + [libm atanh atanhf] [bf bfatanh] [cost 55] + [->c/double (curry format "atanh(~a)")] + [->c/mpfr (curry format "mpfr_atanh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\tanh^{-1} ~a")] + [nonffi atanh]) + +(define-operator/libm (cbrt real) real + [libm cbrt cbrtf] [bf bfcbrt] [cost 80] + [->c/double (curry format "cbrt(~a)")] + [->c/mpfr (curry format "mpfr_cbrt(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sqrt[3]{~a}")] + [nonffi (λ (x) (expt x (/ 1 3)))]) + +(define-operator/libm (ceil real) real + [libm ceil ceilf] [bf bfceiling] [cost 80] + [->c/double (curry format "ceil(~a)")] + [->c/mpfr (curry format "mpfr_ceil(~a, ~a)")] + [->tex (curry format "\\left\\lceil~a\\right\\rceil")] + [nonffi ceiling]) (define (bfcopysign x y) - (bf* (bfabs x) - (bf (expt -1 (bigfloat-signbit y))))) + (bf* (bfabs x) (bf (expt -1 (bigfloat-signbit y))))) + +(define-operator/libm (copysign real real) real + [libm copysign copysignf] [bf bfcopysign] [cost 80] + [->c/double (curry format "copysign(~a, ~a)")] + [->c/mpfr (curry format "mpfr_copysign(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{copysign}\\left(~a, ~a\\right)")] + [nonffi (λ (x y) (if (>= y 0) (abs x) (- (abs x))))]) + +(define-operator/libm (cos real) real + [libm cos cosf] [bf bfcos] [cost 60] + [->c/double (curry format "cos(~a)")] + [->c/mpfr (curry format "mpfr_cos(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\cos ~a")] + [nonffi cos]) + +(define-operator/libm (cosh real) real + [libm cosh coshf] [bf bfcosh] [cost 55] + [->c/double (curry format "cosh(~a)")] + [->c/mpfr (curry format "mpfr_cosh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\cosh ~a")] + [nonffi cosh]) + +(define-operator/libm (erf real) real + [libm erf erff] [bf bferf] [cost 70] + [->c/double (curry format "erf(~a)")] + [->c/mpfr (curry format "mpfr_erf(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{erf} ~a")] + [nonffi erf]) + +(define-operator/libm (erfc real) real + [libm erfc erfcf] [bf bferfc] [cost 70] + [->c/double (curry format "erfc(~a)")] + [->c/mpfr (curry format "mpfr_erfc(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{erfc} ~a")] + [nonffi erfc]) + +(define-operator/libm (exp real) real + [libm exp expf] + [bf exact-exp] [cost 70] + [->c/double (curry format "exp(~a)")] + [->c/mpfr (curry format "mpfr_exp(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "e^{~a}")] + [nonffi exp]) + +(define-operator/libm (exp2 real) real + [libm exp2 exp2f] [bf bfexp2] [cost 70] + [->c/double (curry format "exp2(~a)")] + [->c/mpfr (curry format "mpfr_exp2(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "2^{~a}")] + [nonffi (λ (x) (expt 2 x))]) + +(define-operator/libm (expm1 real) real + [libm expm1 expm1f] [bf bfexpm1] [cost 70] + [->c/double (curry format "expm1(~a)")] + [->c/mpfr (curry format "mpfr_expm1(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "(e^{~a} - 1)^*")] + [nonffi (λ (x) (bigfloat->flonum (bfexpm1 (bf x))))]) + +(define-operator/libm (fabs real) real + [libm fabs fabsf] [bf bfabs] [cost 40] + [->c/double (curry format "fabs(~a)")] + [->c/mpfr (curry format "mpfr_abs(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\left|~a\\right|")] + [nonffi abs]) (define (bffdim x y) (if (bf> x y) (bf- x y) 0.bf)) -(define (bfcube x) - (bf* x (bf* x x))) +(define-operator/libm (fdim real real) real + [libm fdim fdimf] [bf bffdim] [cost 55] + [->c/double (curry format "fdim(~a, ~a)")] + [->c/mpfr (curry format "mpfr_dim(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{fdim}\\left(~a, ~a\\right)")] + [nonffi (λ (x y) (max (- x y) 0))]) + +(define-operator/libm (floor real) real + [libm floor floorf] [bf bffloor] [cost 55] + [->c/double (curry format "floor(~a)")] + [->c/mpfr (curry format "mpfr_floor(~a, ~a)")] + [->tex (curry format "\\left\\lfloor~a\\right\\rfloor")] + [nonffi (λ (x) (floor x))]) (define (bffma x y z) (bf+ (bf* x y) z)) -(define (bflogb x) - (bigfloat-exponent x)) +(define-operator/libm (fma real real real) real + [libm fma fmaf] [bf bffma] [cost 55] + [->c/double (curry format "fma(~a, ~a, ~a)")] + [->c/mpfr (curry format "mpfr_fma(~a, ~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "(~a \\cdot ~a + ~a)_*")] + [nonffi (λ (x y z) (bigfloat->flonum (bf+ (bf* (bf x) (bf y)) (bf z))))]) + +(define-operator/libm (fmax real real) real + [libm fmax fmaxf] [bf bfmax] [cost 55] + [->c/double (curry format "fmax(~a, ~a)")] + [->c/mpfr (curry format "mpfr_fmax(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{fmax}\\left(~a, ~a\\right)")] + [nonffi (λ (x y) (cond [(nan? x) y] [(nan? y) x] [else (max x y)]))]) + +(define-operator/libm (fmin real real) real + [libm fmin fminf] [bf bfmin] [cost 55] + [->c/double (curry format "fmin(~a, ~a)")] + [->c/mpfr (curry format "mpfr_fmin(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{fmin}\\left(~a, ~a\\right)")] + [nonffi (λ (x y) (cond [(nan? x) y] [(nan? y) x] [else (min x y)]))]) (define (bffmod x mod) (bf- x (bf* mod (bffloor (bf/ x mod))))) +(define-operator/libm (fmod real real) real + [libm fmod fmodf] [bf bffmod] [cost 70] + [->c/double (curry format "fmod(~a, ~a)")] + [->c/mpfr (curry format "mpfr_fmod(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "~a \\bmod ~a")] + [nonffi (λ (x y) (bigfloat->flonum (bffmod (bf x) (bf y))))]) + +(define-operator/libm (hypot real real) real + [libm hypot hypotf] [bf bfhypot] [cost 55] + [->c/double (curry format "hypot(~a, ~a)")] + [->c/mpfr (curry format "mpfr_hypot(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sqrt{~a^2 + ~a^2}^*")] + [nonffi (λ (x y) (bigfloat->flonum (bfhypot (bf x) (bf y))))]) + +(define-operator/libm (j0 real) real + [libm j0 j0f] [bf bfbesj0] [cost 55] + [->c/double (curry format "j0(~a)")] + [->c/mpfr (curry format "mpfr_j0(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{j0} ~a")] + [nonffi (λ (x) (bigfloat->flonum (bfbesj0 (bf x))))]) + +(define-operator/libm (j1 real) real + [libm j1 j1f] [bf bfbesj1] [cost 55] + [->c/double (curry format "j1(~a)")] + [->c/mpfr (curry format "mpfr_j1(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{j1} ~a")] + [nonffi (λ (x) (bigfloat->flonum (bfbesj1 (bf x))))]) + +(define-operator/libm (lgamma real) real + [libm lgamma lgammaf] [bf bflog-gamma] [cost 55] + [->c/double (curry format "lgamma(~a)")] + [->c/mpfr (curry format "mpfr_lngamma(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\log_* \\left( \\mathsf{gamma} ~a \\right)")] + [nonffi log-gamma]) + +(define-operator/libm (log real) real + [libm log logf] + [bf exact-log] [cost 70] + [->c/double (curry format "log(~a)")] + [->c/mpfr (curry format "mpfr_log(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\log ~a")] + [nonffi log]) + +(define-operator/libm (log10 real) real + [libm log10 log10f] [bf bflog10] [cost 70] + [->c/double (curry format "log10(~a)")] + [->c/mpfr (curry format "mpfr_log10(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\log_{10} ~a")] + [nonffi (λ (x) (log x 10))]) + +(define-operator/libm (log1p real) real + [libm log1p log1pf] [bf bflog1p] [cost 90] + [->c/double (curry format "log1p(~a)")] + [->c/mpfr (curry format "mpfr_log1p(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\log_* (1 + ~a)")] + [nonffi (λ (x) (bigfloat->flonum (bflog1p (bf x))))]) + +(define-operator/libm (log2 real) real + [libm log2 log2f] [bf bflog2] [cost 70] + [->c/double (curry format "log2(~a)")] + [->c/mpfr (curry format "mpfr_log2(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\log_{2} ~a")] + [nonffi (λ (x) (bigfloat->flonum (bflog2 (bf x))))]) + +(define (bflogb x) + (bffloor (bflog2 (bfabs x)))) + +(define-operator/libm (logb real) real + [libm logb logbf] [bf bflogb] [cost 70] + [->c/double (curry format "logb(~a)")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_get_exp(~a), MPFR_RNDN)")] + [->tex (curry format "\\log^{*}_{b} ~a")] + [nonffi (λ (x) (floor (bigfloat->flonum (bflog2 (bf (abs x))))))]) + +(define-operator/libm (pow real real) real + [libm pow powf] + [args '(2)] [type (hash 2 '(((real real) real) ((complex complex) complex)))] + [bf exact-pow] [cost 210] + [->c/double (curry format "pow(~a, ~a)")] + [->c/mpfr (curry format "mpfr_pow(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "{~a}^{~a}")] + [nonffi expt]) + +(define-operator/libm (remainder real real) real + [libm remainder remainderf] [bf bfremainder] [cost 70] + [->c/double (curry format "remainder(~a, ~a)")] + [->c/mpfr (curry format "mpfr_remainder(~a, ~a, ~a, MPFR_RNDN)")] + [->tex (curry format "~a \\mathsf{rem} ~a")] + [nonffi remainder]) + +(define-operator/libm (rint real) real + [libm rint rintf] [bf bfrint] [cost 70] + [->c/double (curry format "rint(~a)")] + [->c/mpfr (curry format "mpfr_rint(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{rint} ~a")] + [nonffi round]) + +(define-operator/libm (round real) real + [libm round roundf] [bf bfround] [cost 70] + [->c/double (curry format "round(~a)")] + [->c/mpfr (curry format "mpfr_round(~a, ~a)")] + [->tex (curry format "\\mathsf{round} ~a")] + [nonffi round]) + +(define-operator/libm (sin real) real + [libm sin sinf] [bf bfsin] [cost 60] + [->c/double (curry format "sin(~a)")] + [->c/mpfr (curry format "mpfr_sin(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sin ~a")] + [nonffi sin]) + +(define-operator/libm (sinh real) real + [libm sinh sinhf] [bf bfsinh] [cost 55] + [->c/double (curry format "sinh(~a)")] + [->c/mpfr (curry format "mpfr_sinh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sinh ~a")] + [nonffi sinh]) + +(define-operator/libm (sqrt real) real + [libm sqrt sqrtf] + [bf exact-sqrt] [cost 40] + [->c/double (curry format "sqrt(~a)")] + [->c/mpfr (curry format "mpfr_sqrt(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\sqrt{~a}")] + [nonffi sqrt]) + +(define-operator/libm (tan real) real + [libm tan tanf] [bf bftan] [cost 95] + [->c/double (curry format "tan(~a)")] + [->c/mpfr (curry format "mpfr_tan(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\tan ~a")] + [nonffi tan]) + +(define-operator/libm (tanh real) real + [libm tanh tanhf] [bf bftanh] [cost 55] + [->c/double (curry format "tanh(~a)")] + [->c/mpfr (curry format "mpfr_tanh(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\tanh ~a")] + [nonffi tanh]) + +(define-operator/libm (tgamma real) real + [libm tgamma tgammaf] [bf bfgamma] [cost 55] + [->c/double (curry format "tgamma(~a)")] + [->c/mpfr (curry format "mpfr_gamma(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{gamma} ~a")] + [nonffi gamma]) + +(define-operator/libm (trunc real) real + [libm trunc truncf] [bf bftruncate] [cost 55] + [->c/double (curry format "trunc(~a)")] + [->c/mpfr (curry format "mpfr_trunc(~a, ~a)")] + [->tex (curry format "\\mathsf{trunc} ~a")] + [nonffi truncate]) + +(define-operator/libm (y0 real) real + [libm y0 y0f] [bf bfbesy0] [cost 55] + [->c/double (curry format "y0(~a)")] + [->c/mpfr (curry format "mpfr_y0(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{y0} ~a")] + [nonffi (λ (x) (bigfloat->flonum (bfbesy0 (bf x))))]) + +(define-operator/libm (y1 real) real + [libm y1 y1f] [bf bfbesy1] [cost 55] + [->c/double (curry format "y1(~a)")] + [->c/mpfr (curry format "mpfr_y1(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "\\mathsf{y1} ~a")] + [nonffi (λ (x) (bigfloat->flonum (bfbesy1 (bf x))))]) + +;; DEPRECATED + +(define-operator (sqr real) real + [type (hash 1 '(((real) real) ((complex) complex)))] + [fl sqr] [bf exact-sqr] [cost 40] + [->c/double (λ (x) (format "~a * ~a" x x))] + [->c/mpfr (curry format "mpfr_sqr(~a, ~a, MPFR_RNDN)")] + [->tex (curry format "{~a}^2")] + [nonffi (λ (x) (* x x))]) + +(define-operator (cube real) real + [fl (λ (x) (* x (* x x)))] [bf (λ (x) (bf* x (bf* x x)))] [cost 80] + [->c/double (λ (x) (format "~a * (~a * ~a)" x x x))] + [->c/mpfr (λ (out x) (format "mpfr_sqr(~a, ~a, MPFR_RNDN); mpfr_mul(~a, ~a, ~a, MPFR_RNDN)" out x out out x))] + [->tex (curry format "{~a}^3")] + [nonffi (λ (x) (* x x x))]) + (define (if-fn test if-true if-false) (if test if-true if-false)) (define (and-fn . as) (andmap identity as)) (define (or-fn . as) (ormap identity as)) @@ -139,122 +531,136 @@ (for/and ([left args] [right (cdr args)]) (test left right))) -; Table defining costs and translations to bigfloat and regular float -; See "costs.c" for details of how these costs were determined -(define-table operations - ; arithmetic - [+ '(2) bf+ + 40] - [- '(1 2) bf- - 40] - [* '(2) bf* * 40] - [/ '(2) bf/ / 40] - - [sqr '(1) bfsqr _flsqr 40] - [cube '(1) bfcube _flcube 80] - - [acos '(1) bfacos _flacos 90] - [acosh '(1) bfacosh _flacosh 55] - [asin '(1) bfasin _flasin 105] - [asinh '(1) bfasinh _flasinh 55] - [atan '(1) bfatan _flatan 105] - [atan2 '(2) bfatan2 _flatan2 140] - [atanh '(1) bfatanh _flatanh 55] - [cbrt '(1) bfcbrt _flcbrt 80] - [ceil '(1) bfceiling _flceil 80] - [copysign '(2) bfcopysign _flcopysign 80] - [cos '(1) bfcos _flcos 60] - [cosh '(1) bfcosh _flcosh 55] - [erf '(1) bferf _flerf 70] - [erfc '(1) bferfc _flerfc 70] - [exp '(1) bfexp _flexp 70] - [exp2 '(1) bfexp2 _flexp2 70] - [expm1 '(1) bfexpm1 _flexpm1 70] - [fabs '(1) bfabs _flfabs 40] - [fdim '(2) bffdim _flfdim 55] - [floor '(1) bffloor _flfloor 55] - [fma '(3) bffma _flfma 55] - [fmax '(2) bfmax _flfmax 55] - [fmin '(2) bfmin _flfmin 55] - [fmod '(2) bffmod _flfmod 70] - [hypot '(2) bfhypot _flhypot 55] - [j0 '(1) bfbesj0 _flj0 55] - [j1 '(1) bfbesj1 _flj1 55] - [lgamma '(1) bflog-gamma _fllgamma 55] - [log '(1) bflog _fllog 70] - [log10 '(1) bflog10 _fllog10 70] - [log1p '(1) bflog1p _fllog1p 90] - [log2 '(1) bflog2 _fllog2 70] - [logb '(1) bflogb _fllogb 70] - [pow '(2) bfexpt _flpow 210] - [remainder '(2) bfremainder _flremainder 70] - [rint '(1) bfrint _flrint 70] - [round '(1) bfround _flround 70] - [sin '(1) bfsin _flsin 60] - [sinh '(1) bfsinh _flsinh 55] - [sqrt '(1) bfsqrt _flsqrt 40] - [tan '(1) bftan _fltan 95] - [tanh '(1) bftanh _fltanh 55] - [tgamma '(1) bfgamma _fltgamma 55] - [trunc '(1) bftruncate _fltrunc 55] - [y0 '(1) bfbesy0 _fly0 55] - [y1 '(1) bfbesy1 _fly1 55] - - ; TODO : These are different and should be treated differently - [if '(3) if-fn if-fn 65] - [== '(*) (comparator bf=) (comparator =) 65] - [!= '(*) bf!=-fn !=-fn 65] - [> '(*) (comparator bf>) (comparator >) 65] - [< '(*) (comparator bf<) (comparator <) 65] - [>= '(*) (comparator bf>=) (comparator >=) 65] - [<= '(*) (comparator bf<=) (comparator <=) 65] - [not '(1) not not 65] - [and '(*) and-fn and-fn 55] - [or '(*) or-fn or-fn 55]) - -(define *operations* (make-parameter operations)) - -(define constants '(PI E TRUE FALSE)) - -(define predicates '(not or and < > <= >= == !=)) - -(define mode:args 0) -(define mode:bf 1) -(define mode:fl 2) -(define mode:cost 3) - -(define (variable? var) - (and (symbol? var) (not (member var constants)))) +(define-operator (if bool real real) real ; types not used, special cased in type checker + [fl if-fn] [bf if-fn] [cost 65] + [->c/double (curry format "~a ? ~a : ~a")] + [->c/mpfr + (λ (out c a b) + (format "if (mpfr_get_si(~a, MPFR_RNDN)) { mpfr_set(~a, ~a, MPFR_RNDN); } else { mpfr_set(~a, ~a, MPFR_RNDN); }" c out a out b))] + [->tex (curry format "~a ? ~a : ~a")] + [nonffi if-fn]) + +(define ((infix-joiner str) . args) + (string-join args str)) + +(define-operator (== real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl (comparator =)] [bf (comparator bf=)] [cost 65] + [->c/double (curry format "~a == ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) == 0, MPFR_RNDN)")] ; TODO: cannot handle variary = + [->tex (infix-joiner " = ")] + [nonffi (comparator =)]) + +(define-operator (complex real real) complex + ; Override number of arguments + [fl make-rectangular] [bf bigcomplex] [cost 0] + [->c/double (const "/* ERROR: no complex support in C */")] + [->c/mpfr (const "/* ERROR: no complex support in C */")] + [->tex (curry format "~a + ~a i")] + [nonffi make-rectangular]) + +(define-operator (re complex) real + ; Override number of arguments + [fl real-part] [bf bigcomplex-re] [cost 0] + [->c/double (const "/* ERROR: no complex support in C */")] + [->c/mpfr (const "/* ERROR: no complex support in C */")] + [->tex (curry format "\\Re(~a)")] + [nonffi real-part]) + +(define-operator (im complex) real + ; Override number of arguments + [fl imag-part] [bf bigcomplex-im] [cost 0] + [->c/double (const "/* ERROR: no complex support in C */")] + [->c/mpfr (const "/* ERROR: no complex support in C */")] + [->tex (curry format "\\Im(~a)")] + [nonffi imag-part]) + +(define-operator (conj complex) real + ; Override number of arguments + [fl conjugate] [bf bf-complex-conjugate] [cost 0] + [->c/double (const "/* ERROR: no complex support in C */")] + [->c/mpfr (const "/* ERROR: no complex support in C */")] + [->tex (curry format "\\overline{~a}")] + [nonffi conjugate]) + +(define-operator (!= real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl !=-fn] [bf bf!=-fn] [cost 65] + [->c/double (curry format "~a != ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) != 0, MPFR_RNDN)")] ; TODO: cannot handle variary != + [->tex (infix-joiner " \\ne ")] + [nonffi !=-fn]) + +(define-operator (< real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl (comparator <)] [bf (comparator bf<)] [cost 65] + [->c/double (curry format "~a < ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) < 0, MPFR_RNDN)")] ; TODO: cannot handle variary < + [->tex (infix-joiner " \\lt ")] + [nonffi (comparator <)]) + +(define-operator (> real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl (comparator >)] [bf (comparator bf>)] [cost 65] + [->c/double (curry format "~a > ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) > 0, MPFR_RNDN)")] ; TODO: cannot handle variary > + [->tex (infix-joiner " \\gt ")] + [nonffi (comparator >)]) + +(define-operator (<= real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl (comparator <=)] [bf (comparator bf<=)] [cost 65] + [->c/double (curry format "~a <= ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) <= 0, MPFR_RNDN)")] ; TODO: cannot handle variary <= + [->tex (infix-joiner " \\le ")] + [nonffi (comparator <=)]) + +(define-operator (>= real real) bool + ; Override number of arguments + [type #hash((* . (((* real) bool))))] [args '(*)] + [fl (comparator >=)] [bf (comparator bf>=)] [cost 65] + [->c/double (curry format "~a >= ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_cmp(~a, ~a) >= 0, MPFR_RNDN)")] ; TODO: cannot handle variary >= + [->tex (infix-joiner " \\ge ")] + [nonffi (comparator >=)]) + +(define-operator (not bool) bool + [fl not] [bf not] [cost 65] + [->c/double (curry format "!~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, !mpfr_get_si(~a, MPFR_RNDN), MPFR_RNDN)")] + [->tex (curry format "\\neg ~a")] + [nonffi not]) + +(define-operator (and bool bool) bool + ; Override number of arguments + [type #hash((* . (((* bool) bool))))] [args '(*)] + [fl and-fn] [bf and-fn] [cost 55] + [->c/double (curry format "~a && ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_get_si(~a, MPFR_RNDN) && mpfr_get_si(~a, MPFR_RNDN), MPFR_RNDN)")] + [->tex (infix-joiner " \\land ")] + [nonffi and-fn]) + +(define-operator (or bool bool) bool + ; Override number of arguments + [type #hash((* . (((* bool) bool))))] [args '(*)] + [fl or-fn] [bf or-fn] [cost 55] + [->c/double (curry format "~a || ~a")] + [->c/mpfr (curry format "mpfr_set_si(~a, mpfr_get_si(~a, MPFR_RNDN) || mpfr_get_si(~a, MPFR_RNDN), MPFR_RNDN)")] + [->tex (infix-joiner " \\lor ")] + [nonffi or-fn]) + +(define (operator? op) + (and (symbol? op) (dict-has-key? (cdr operators) op))) (define (constant? var) - (or (member var constants) (number? var))) - -(define (->flonum x) - (let ([convert ((flag 'precision 'double) - real->double-flonum - real->single-flonum)]) - (cond - [(real? x) (convert x)] - [(bigfloat? x) (convert (bigfloat->flonum x))] - [(complex? x) - (if (= (imag-part x) 0) - (->flonum (real-part x)) - +nan.0)] - [(eq? x 'PI) (convert pi)] - [(eq? x 'E) (convert (exp 1.0))] - [(eq? x 'TRUE) #t] - [(eq? x 'FALSE) #f] - [else x]))) - -(define (->bf x) - (cond - [(real? x) (bf x)] - [(bigfloat? x) x] - [(complex? x) - (if (= (imag-part x) 0) (->bf (real-part x)) +nan.bf)] - [(eq? x 'PI) pi.bf] - [(eq? x 'E) (bfexp 1.bf)] - [(eq? x 'TRUE) #t] - [(eq? x 'FALSE) #f] - [else x])) - -(define (real-op->bigfloat-op op) (list-ref (hash-ref (*operations*) op) mode:bf)) -(define (real-op->float-op op) (list-ref (hash-ref (*operations*) op) mode:fl)) + (or (number? var) + (and (symbol? var) + (dict-has-key? (cdr constants) var)))) + +(define (variable? var) + (and (symbol? var) (not (constant? var)))) diff --git a/src/test/exacts-test.rkt b/src/test/exacts-test.rkt deleted file mode 100644 index f993194f0..000000000 --- a/src/test/exacts-test.rkt +++ /dev/null @@ -1,24 +0,0 @@ -#lang racket - -(require rackunit) -(require math/bigfloat) -(require "../config.rkt") -(require "../programs.rkt") -(require "../points.rkt") -(require "../format/test.rkt") - -(define exacts-test - (test-suite - "Test that exact evaluation matches 65536-bit floating point" - (for ([test benchmark-path]) - (bf-precision (*precision-step*)) - (printf ">> ~a" (test-program test)) - (define ctx (prepare-points (test-program test) (test-samplers test) (test-precondition test))) - (printf " (at ~a bits)\n" (bf-precision)) - (bf-precision 65536) - (define f (eval-prog (test-program test) mode:bf)) - (for ([(pt ex) (in-pcontext ctx)]) - (with-check-info - (['point pt] ['program (test-program test)]) - (when (not (= ex (f pt))) - (check-equal? ex (f pt)))))))) diff --git a/src/test/num-points-test-results.csv b/src/test/num-points-test-results.csv deleted file mode 100644 index 4545c5193..000000000 --- a/src/test/num-points-test-results.csv +++ /dev/null @@ -1,10 +0,0 @@ - -4 , 294.697 , 689.311 -8 , 478.589 , 811.82 -16 , 589.091 , 1022.4 -32 , 610.362 , 1080.16 -64 , 650.086 , 820.024 -128 , 673.105 , 958.568 -256 , 742.152 , 945.029 -512 , 746.264 , 1090.57 -1024 , 715.619 , 1657.11 diff --git a/src/test/num-points-test.sh b/src/test/num-points-test.sh deleted file mode 100644 index faa63f6e0..000000000 --- a/src/test/num-points-test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -x -# RUN this from herbie root! -echo "" > test/num-points-test-results.csv -for power in `seq 2 11`; do - points=`awk 'BEGIN{print 2^'$power'}'` - racket reports/make-report.rkt -s $points bench/hamming/ - racket compile/results-to-csv.rkt graphs/results.herbie.dat graphs/results.herbie.csv - awk -F , 'BEGIN{totalPointsRecovered=0; totalTime=0}\ -{ totalPointsRecovered += ($7 - $8); totalTime += $6}\ -END {print '$points',",",totalPointsRecovered, ",", (totalTime / 1000)}' graphs/results.herbie.csv >> test/num-points-test-results.csv -done diff --git a/src/test/regimes-test.rkt b/src/test/regimes-test.rkt deleted file mode 100644 index fe3155a02..000000000 --- a/src/test/regimes-test.rkt +++ /dev/null @@ -1,126 +0,0 @@ -#lang racket - -(require rackunit) -(require "../common.rkt") -(require "../points.rkt") -(require "../float.rkt") -(require "../alternative.rkt") -(require "../glue.rkt") -(require "../syntax/syntax.rkt") -(require "../syntax/rules.rkt") -(require math/bigfloat) - -(error "Does not work now that the ruleset framework has changed.") - -(define *rulesets* '()) - -(define-ruleset dummy-rules - [f->g1 (f x) (g1 x)] - [f->g2 (f x) (g2 x)] - [f->g3 (f x) (g3 x)] - [f->g4 (f x) (g4 x)] - [f->g5 (f x) (g5 x)] - [f->g6 (f x) (g6 x)]) - -(define bfg1 (const (bf 10))) -(define bfg2 (const (bf 20))) -(define bfg3 (const (bf 30))) -(define bfg4 (const (bf 40))) -(define bfg5 (const (bf 50))) -(define bfg6 (const (bf 60))) - -(define flg1 (const 10.0)) -(define flg2 (const 20.0)) -(define flg3 (const 30.0)) -(define flg4 (const 40.0)) -(define flg5 (const 50.0)) -(define flg6 (const 60.0)) - -(define (if-fn test if-true if-false) (if test if-true if-false)) -(define (and-fn . as) (andmap identity as)) -(define (or-fn . as) (ormap identity as)) - -(define-table dummy-operations - [g1 bfg1 flg1 1] - [g2 bfg2 flg2 1] - [g3 bfg3 flg3 1] - [g4 bfg4 flg4 1] - [g5 bfg5 flg5 1] - [g6 bfg6 flg6 1] - [if if-fn if-fn 1] - [> bf> > 1] - [< bf< < 1] - [<= bf<= <= 1] - [>= bf>= >= 1] - [and and-fn and-fn 1] - [or or-fn or-fn 1]) - -(define (synthesize-op op1 op2 split) - (let ([bfop1 (list-ref (hash-ref dummy-operations op1) 0)] - [bfop2 (list-ref (hash-ref dummy-operations op2) 0)] - [bfsplit (map bf split)]) - (hash-set! dummy-operations 'f - (list (λ (x) - (if (bf< x (car bfsplit)) - (bfop1 x) - (bfop2 x))) - (λ (x) 0.0) - 1)))) - -(define (get-branch-info prog) - (match prog - [`(λ ,vars (if (< ,var ,branch) (,f1 ,var) (,f2 ,var))) - (values branch f1 f2)] - [_ (values +inf.0 #f #f)])) - -(define (test-regimes) - (define branch-point (sample-float)) - (define (rand-op) - (car (rule-output (list-ref dummy-rules (random 6))))) - (debug #:from 'test-regimes "Selected branch point " branch-point) - (define op1 (rand-op)) - (define op2 (let loop () - (let ([op (rand-op)]) - (if (eq? op op1) - (loop) - op)))) - (debug #:from 'test-regimes "Selected ops" op1 "and" op2) - (synthesize-op op1 op2 branch-point) - (let*-values ([(res) (alt-program - (parameterize ([*rules* dummy-rules] [*operations* dummy-operations]) - (improve '(λ (x) (f x)) (*num-iterations*))))] - [(actual-branch actual-left-func actual-right-func) - (get-branch-info res)] - [(expected-branch) (car branch-point)]) - (list actual-branch - expected-branch - (list op1 actual-left-func) - (list op2 actual-right-func)))) - -(define (error-of res) - (bit-difference (car res) (cadr res))) - -(define (rel-error-of res) - (/ (abs (- (car res) (cadr res))) (car res))) - - -(define (gen-file n filename) - (write-file filename - (gen-data n))) - -(define (gen-data n) - (for ([i (range n)]) - (let* ([res (test-regimes)] - [bits (error-of res)] - [rel (rel-error-of res)] - [actual (car res)] - [expected (cadr res)] - [op1 (third res)] - [op2 (fourth res)]) - (printf "~a, ~a, ~a, ~a, ~a, ~a\n" actual expected bits rel op1 op2)))) - -(define regimes-tests - (test-suite "Test that regimes finds the correct branch point, within a bit." - (for ([i (range 3)]) - (let* ([res (test-regimes)]) - (check-true (2 . > . (error-of res))))))) diff --git a/src/type-check.rkt b/src/type-check.rkt new file mode 100644 index 000000000..2d7968cb4 --- /dev/null +++ b/src/type-check.rkt @@ -0,0 +1,125 @@ +#lang racket +(require "common.rkt" "syntax/syntax.rkt" "errors.rkt") +(provide assert-program-type! assert-expression-type! type-of get-sigs argtypes->rtype) + +(define (get-sigs fun-name num-args) + (if (and (operator? fun-name) (hash-has-key? (operator-info fun-name 'type) num-args)) + (hash-ref (operator-info fun-name 'type) num-args) + (if (hash-has-key? (operator-info fun-name 'type) '*) + (hash-ref (operator-info fun-name 'type) '*) + #f))) + +(define (argtypes->rtype argtypes sig) + (match sig + [`((* ,argtype) ,rtype) + (and (andmap (curry equal? argtype) argtypes) rtype)] + [`((,expected-types ...) ,rtype) + (and (andmap equal? argtypes expected-types) rtype)])) + +;; Unit tests +;; Rewrite expression->type so that expr is a syntax object +;; Collect errors somewhere +;; error! is a function that takes (stx format . args) and puts it somewhere +(define (assert-program-type! stx) + (match-define (list (app syntax-e 'FPCore) (app syntax-e (list vars ...)) props ... body) (syntax-e stx)) + (assert-expression-type! body 'real #:env (for/hash ([var vars]) (values (syntax-e var) 'real)))) + +(define (assert-expression-type! stx expected-rtype #:env [env #hash()]) + (define errs + (reap [sow] + (define (error! stx fmt . args) + (sow (cons stx (apply format fmt args)))) + (define actual-rtype (expression->type stx env error!)) + (unless (equal? expected-rtype actual-rtype) + (error! stx "Expected program of type ~a, got type ~a" expected-rtype actual-rtype)))) + (unless (null? errs) + (raise-herbie-syntax-error "Program has type errors" #:locations errs))) + +(define (type-of expr env) + (expression->type (datum->syntax #f expr) env + (lambda (stx msg . args) + (error "Unexpected call to error! within type-of" + stx (apply format msg args))))) + + +(define (expression->type stx env error!) + (match stx + [(or #`TRUE #`FALSE) 'bool] + [#`,(? constant? x) 'real] + [#`,(? variable? x) (dict-ref env x)] + [#`(,(and (or '+ '- '* '/) op) #,exprs ...) + (define t 'real) + (for ([arg exprs] [i (in-naturals)]) + (define actual-type (expression->type arg env error!)) + (if (= i 0) (set! t actual-type) #f) + (unless (equal? t actual-type) + (error! stx "~a expects argument ~a of type ~a (not ~a)" op (+ i 1) t actual-type))) + t] + [#`(,(? operator? op) #,exprs ...) + (define sigs (get-sigs op (length exprs))) + (unless sigs (error "Operator ~a has no type signature of length ~a" op (length exprs))) + + (define actual-types (for/list ([arg exprs]) (expression->type arg env error!))) + (define rtype + (for/or ([sig sigs]) + (argtypes->rtype actual-types sig))) + (unless rtype + (error! stx "Invalid arguments to ~a; expects ~a but got (~a ~a)" op + (string-join + (for/list ([sig sigs]) + (match sig + [`((* ,atype) ,rtype) + (format "(~a <~a> ...)" op atype)] + [`((,atypes ...) ,rtype) + (format "(~a ~a)" op (string-join (map (curry format "<~a>") atypes) " "))])) + " or ") + op (string-join (map (curry format "<~a>") actual-types) " "))) + rtype] + [#`(let ((,id #,expr) ...) #,body) + (define env2 + (for/fold ([env2 env]) ([var id] [val expr]) + (dict-set env2 var (expression->type val env error!)))) + (expression->type body env2 error!)] + [#`(if #,branch #,ifstmt #,elsestmt) + (define branch-type (expression->type branch env error!)) + (unless (equal? branch-type 'bool) + (error! stx "If statement has non-boolean type for branch ~a" branch-type)) + (define ifstmt-type (expression->type ifstmt env error!)) + (define elsestmt-type (expression->type elsestmt env error!)) + (unless (equal? ifstmt-type elsestmt-type) + (error! stx "If statement has different types for if (~a) and else (~a)" ifstmt-type elsestmt-type)) + + ifstmt-type])) + +(module+ test + (require rackunit) + + (define (fail stx msg . args) + (error (apply format msg args) stx)) + + (define (check-type type expr #:env [env #hash()]) + (check-equal? (expression->type expr env fail) type)) + + (define (check-fails expr #:env [env #hash()]) + (check-equal? + (let ([v #f]) + (expression->type expr env (lambda _ (set! v #t))) + v) + #t)) + + (check-type 'real #'4) + (check-type 'real #'x #:env #hash((x . real))) + (check-type 'real #'(acos x) #:env #hash((x . real))) + (check-fails #'(acos x) #:env #hash((x . bool))) + (check-type 'bool #'(and a b c) #:env #hash((a . bool) (b . bool) (c . bool))) + (check-type 'real #'(if (== a 1) 1 0) #:env #hash((a . real))) + (check-fails #'(if (== a 1) 1 0) #:env #hash((a . bool))) + (check-fails #'(if (== a 1) 1 TRUE) #:env #hash((a . real))) + (check-type 'bool #'(let ([a 1]) TRUE)) + (check-type 'real #'(let ([a 1]) a) #:env #hash((a . bool))) + + (check-type 'complex #'(complex 2 3)) + (check-type 'complex #'(+ (complex 1 2) (complex 3 4))) + (check-fails #'(+ 2 (complex 1 2))) + (check-type 'real #'(+)) + (check-type 'real #'(re (+ (complex 1 2) (complex 3 4))))) diff --git a/src/web/arrow-chart.js b/src/web/arrow-chart.js index cdb11e2f5..ea5069e80 100644 --- a/src/web/arrow-chart.js +++ b/src/web/arrow-chart.js @@ -135,6 +135,10 @@ function draw_results(node) { BADGES.container.classList.add("highlight-one"); }); ARROWS[i].addEventListener("mouseout", clear); + ARROWS[i].addEventListener("click", function() { + var id = "link" + ARROWS[i].attributes["data-id"].value; + document.getElementById(id).click(); + }); })(i); } }); diff --git a/src/web/demo.js b/src/web/demo.js index 5e16cf5ee..4d68bbdeb 100644 --- a/src/web/demo.js +++ b/src/web/demo.js @@ -4,7 +4,16 @@ FUNCTIONS = { "sqrt": [1], "sqr": [1], "exp": [1], "log": [1], "pow": [2], "sin": [1], "cos": [1], "tan": [1], "cot": [1], "asin": [1], "acos": [1], "atan": [1], - "sinh": [1], "cosh": [1], "tanh": [1] + "sinh": [1], "cosh": [1], "tanh": [1], + "asinh": [1], "acosh": [1], "atanh": [1], + "cbrt": [1], "cube": [1], "ceil": [1], "copysign": [2], + "erf": [1], "erfc": [1], "exp2": [1], "expm1": [1], + "fdim": [2], "floor": [1], "fma": [3], "fmax": [2], + "fmin": [2], "fmod": [2], "hypot": [2], + "j0": [1], "j1": [1], "lgamma": [1], "log10": [1], + "log1p": [1], "log2": [1], "logb": [1], + "remainder": [2], "rint": [1], "round": [1], + "tgamma": [1], "trunc": [1], "y0": [1], "y1": [1] } SECRETFUNCTIONS = {"^": "pow", "**": "pow", "abs": "fabs"} diff --git a/src/web/demo.rkt b/src/web/demo.rkt index 35c29bc97..14efc98a6 100644 --- a/src/web/demo.rkt +++ b/src/web/demo.rkt @@ -1,5 +1,5 @@ #lang racket -(require openssl/sha1 xml) +(require openssl/sha1 (rename-in xml [location? xml-location?])) (require web-server/servlet web-server/servlet-env web-server/dispatch web-server/dispatchers/dispatch web-server/dispatch/extend web-server/http/bindings web-server/configuration/responders @@ -7,6 +7,7 @@ (require "../sandbox.rkt") (require "../formats/datafile.rkt" "../reports/make-graph.rkt" "../reports/make-report.rkt" "../reports/thread-pool.rkt") (require "../formats/tex.rkt") +(require "../syntax-check.rkt" "../type-check.rkt") (require "../common.rkt" "../config.rkt" "../programs.rkt" "../formats/test.rkt" "../errors.rkt") (require "../web/common.rkt") @@ -37,6 +38,7 @@ [("improve-start") #:method "post" improve-start] [("improve") #:method "post" improve] [("check-status" (string-arg)) check-status] + [((hash-arg) "interactive.js") generate-interactive] [((hash-arg) "graph.html") generate-report] [((hash-arg) "debug.txt") generate-debug] [((hash-arg) (string-arg)) generate-plot])) @@ -91,11 +93,23 @@ (function-list '((+ - * / abs) "The usual arithmetic functions") '((sqrt sqr) "Squares and square roots") + '((cbrt cube) "Cubes and cube roots") '((exp log) "Natural exponent and natural log") '((expt) "Raising a value to an exponent (also called " (code "pow") ")") '((sin cos tan) "The trigonometric functions") '((asin acos atan) "The inverse trigonometric functions") '((sinh cosh tanh) "The hyperbolic trigonometric functions") + '((asinh acosh atanh) "The inverse hyperbolic trigonometric functions") + '((ceil floor rint round trunc) "Rounding functions") + '((erf erfc) "Error function and complementary error function") + '((exp2 log2 log10) "Exponential base 2, log base 2, and log base 10") + '((fmod remainder) "Mod and remainder functions") + '((j0 j1 y0 y1) "Bessel functions of the first and second kind") + '((tgamma lgamma) "The gamma function and log gamma function") + '((fmin fmax) "The min and max functions") + '((fdim copysign) "The positive difference and copysign functions") + '((expm1 log1p) "The exponent of " (code "x - 1") " and the log of " (code "1 + x")) + '((fma hypot logb) "The fma, hypotenuse (distance from origin), and logb functions") '((PI E) "The mathematical constants")) `(p (em "Note") ": " @@ -111,8 +125,6 @@ (define *completed-jobs* (make-hash)) (define *jobs* (make-hash)) -(define *fixed-seed* #(2775764126 3555076145 3898259844 1891440260 2599947619 1948460636)) - (define *worker-thread* (thread (λ () @@ -151,7 +163,11 @@ ;; Output results (make-directory (build-path (*demo-output*) path)) (define make-page - (cond [(test-result? result) (λ args (apply make-graph args) (apply make-plots args))] + (cond [(test-result? result) (λ args + (apply make-graph + (append args + (list (string? (get-interactive-js result))))) + (apply make-plots args))] [(test-timeout? result) make-timeout] [(test-failure? result) make-traceback])) (with-output-to-file (build-path (*demo-output*) path "graph.html") @@ -171,7 +187,7 @@ (define (update-report result dir seed data-file html-file) (define link (path-element->string (last (explode-path dir)))) - (define data (get-table-data result link)) + (match-define (cons _ data) (get-table-data result link)) (define info (if (file-exists? data-file) (let ([info (read-datafile data-file)]) @@ -207,6 +223,7 @@ "Please " (a ([href ,go-back]) "go back") " and try again.")))]) (assert-program! formula) + (assert-program-type! formula) (define hash (sha1 (open-input-string formula-str))) (body hash formula))] [_ @@ -248,6 +265,15 @@ (redirect-to (add-prefix (format "~a.~a/graph.html" hash *herbie-commit*)) see-other)) (url main))) +(define (generate-interactive req results) + (match-define (cons result debug) results) + + (response 200 #"OK" (current-seconds) #"text" + (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (hash-count *jobs*))))) + (λ (out) + (parameterize ([current-output-port out]) + (output-interactive-js result (format "~a.~a" hash *herbie-commit*) #f))))) + (define (generate-report req results) (match-define (cons result debug) results) @@ -255,7 +281,10 @@ (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (hash-count *jobs*))))) (λ (out) (parameterize ([current-output-port out]) - (make-graph result (format "~a.~a" hash *herbie-commit*) #f))))) + (make-graph result + (format "~a.~a" hash *herbie-commit*) + #f + (string? (get-interactive-js result))))))) (define (generate-plot req results plotname) (match-define (cons result debug) results) @@ -294,6 +323,9 @@ timeout ,(*timeout*) output-dir ,(*demo-output*) reeval ,(*reeval-pts*) demo? ,(*demo?*))) (thread-send *worker-thread* config) + (eprintf "Herbie ~a with seed ~a\n" *herbie-version* (get-seed)) + (eprintf "Find help on , exit with Ctrl-C\n") + (serve/servlet dispatch #:listen-ip (if (*demo?*) #f "127.0.0.1") diff --git a/src/web/graph.css b/src/web/graph.css index 2ce507130..ceb51c2e7 100644 --- a/src/web/graph.css +++ b/src/web/graph.css @@ -23,9 +23,9 @@ section h1 { @media print { #program { padding: 0; background: transparent; margin: 2em 0; } } #program .program { display: inline-block; } #program .arrow { color: transparent; font-size: 0; } -#program .arrow:after { content: "⬇"; color: black; font-size: 24px; } +#program .arrow:after { content: "↓"; color: black; font-size: 24px; } #program.horizontal .arrow { display: inline-block; } -#program.horizontal .arrow:after { margin: 0 1em; content: "⮕" } +#program.horizontal .arrow:after { margin: 0 1em; content: "→"; font-size: 40px; } #graphs figure { margin: 1em auto; position: relative; padding-top: 300px; width: 800px; } #graphs figcaption { text-align: left; } @@ -41,6 +41,27 @@ section h1 { #graphs figcaption button.Input { border-color: red; background: pink; } #graphs figcaption button.inactive { background: white; } +#try-result output { font-size: 108%; margin: 0 .5em; float: right; } +#try-result div { overflow: auto; } +#try-result table { line-height: 1.5; margin-top: .25em; } +#try-result { width: 39%; float: right; } +#try-result p.header { margin: 0 0 .5em; font-size: 120%; color: #444; border-bottom: 1px solid #ccc; line-height: 1.5; } +#try-result label:after { content: ":"; } + +#try-inputs-wrapper { width: 59%; display: inline-block; } + +#try-inputs ol { list-style: none; padding: 0; display: inline-block; margin: 0 0 0 -1em; } +#try-inputs ol label { min-width: 4ex; text-align: right; margin-right: .5em; display: inline-block; } +#try-inputs li { margin-left: 1em; display: inline-block; font-size: 110%; font-family: monospace; line-height: 2; } +#try-inputs label:after { content: ":"; } +#try-inputs input { padding: 1px 4px; width: 25ex; } +#try-inputs p.header { margin: 0 0 .5em; font-size: 120%; color: #444; border-bottom: 1px solid #ccc; line-height: 1.5; } + +#try-error { color: red; font-size: 120%; display: none; } + +.error #try-error { display: block; } +#try-result.error table { display: none; } + .tabbar { margin: -1.25em 0 0; padding: 0; list-style-type: none; list-style-position: inside; text-align: left; } .tabbar p { display: inline-block; margin: 0 .5em 0 0} .tabbar li { padding: .5ex; display: inline-block; margin: .1em; cursor: pointer; } diff --git a/src/web/report.css b/src/web/report.css index 07e6afe8c..2d72fc920 100644 --- a/src/web/report.css +++ b/src/web/report.css @@ -42,11 +42,12 @@ li.eq-target {background-color: #87fc70;} li.lt-target {background-color: #ffdb4c;} li.eq-start {background-color: #ff9500;} li.lt-start {background-color: #ff5e3a; color: #f7f7f7;} -li.crash {background-color: #4a4a4a; color: #f7f7f7;} +#test-badges li.crash {background-color:#ff9d87; color:#ed2b00; border: 2px solid #ff5e3a; width: 71px; height: 36px; line-height: 36px; } +li.error {background-color: #4a4a4a; color: #f7f7f7;} li.timeout {background-color: #8e8e93; color: #f7f7f7;} #about { margin: 3em auto; } -#about th { font-weight: bold; } +#about th { font-weight: bold; text-align: left; padding-right: 1em; } #flag-list { position: relative; } #flag-list kbd:not(:last-child):after {content: ", ";} @@ -59,6 +60,7 @@ li.timeout {background-color: #8e8e93; color: #f7f7f7;} #flag-list.changed-flags #all-flags { display: none; } #results { border-collapse: collapse; width:100%; } +#results th { white-space: pre; } #results td { text-align: right; padding: .5em; overflow: hidden; font-size: 15pt; } /* Removed because it was confusing and rarely useful */ /*#results td.bad-est {border-right: 5px solid #c86edf;}*/ @@ -69,7 +71,7 @@ li.timeout {background-color: #8e8e93; color: #f7f7f7;} padding: .5em; margin-top: -1.25em; } -#results td:nth-child(1) {text-align: left;} +#results td:nth-child(1) { text-align: left; word-break: break-all; } #results td:nth-child(7) {width: 0;} tr.imp-start td:nth-child(3) {background-color:#87fc70;} @@ -88,10 +90,20 @@ tr.eq-start td:nth-child(4) {background-color:#ffdb4c;} tr.lt-start td:nth-child(3) {background-color:#ff5e3a;color: #f7f7f7;} tr.lt-start td:nth-child(4) {background-color:#ffdb4c;color: #f7f7f7;} -tr.crash td:nth-child(3) {background-color:#4a4a4a;color:#f7f7f7;} +tr.crash td:nth-child(3) {background-color:#ff9d87; color:#ed2b00; border: 2px solid #ff5e3a; margin: -2px; } +tr.error td:nth-child(3) {background-color:#4a4a4a;color:#f7f7f7;} tr.timeout td:nth-child(3) {background-color:#8e8e93;color:#f7f7f7;} #results.no-target td:nth-child(4) {display: none;} #results.no-target th:nth-child(4) {display: none;} #results.no-inf td:nth-child(5) {display: none;} #results.no-inf th:nth-child(5) {display: none;} + +.help-button { + display: inline-block; background: #888; + font-size: .8em; color: #eee; line-height: 1.3em; + height: 1.25em; width: 1.25em; border-radius: .625em; + vertical-align: top; +} + +.help-button:hover { background: #444; cursor: pointer; } diff --git a/src/web/report.js b/src/web/report.js index 0dfabc5aa..2c088e99e 100644 --- a/src/web/report.js +++ b/src/web/report.js @@ -70,6 +70,28 @@ function select_tab(id) { pane.style.display = "block"; } +function submit_inputs() { + var originalOutputElem = document.querySelector('#try-original-output'); + var herbieOutputElem = document.querySelector('#try-herbie-output'); + var inputs = document.querySelectorAll('#try-inputs input'); + var inputVals = []; + for (var i = 0; i < inputs.length; i++) { + var val = parseFloat(inputs[i].value); + if (isNaN(val)) { + if (inputs[i].value.length != 0) { + // Don't update error message if there is no input + document.querySelector('#try-result').className = 'error' + } + return; + } else { + document.querySelector('#try-result').className = 'no-error' + inputVals.push(val); + } + } + originalOutputElem.innerHTML = start.apply(null, inputVals); + herbieOutputElem.innerHTML = end.apply(null, inputVals); +} + function setup_figure_tabs(figure_container) { var figures = figure_container.getElementsByTagName("figure"); var figure_array = {}; @@ -138,6 +160,8 @@ function load_graph() { setup_timeline(); // Run the program_arrow after rendering happens MathJax.Hub.Queue(setup_program_arrow); + // Submit the default vals in the "Try it out" section + submit_inputs() } function load_report() { diff --git a/src/web/session.rkt b/src/web/session.rkt index 5746fbd8c..5515ed1ae 100644 --- a/src/web/session.rkt +++ b/src/web/session.rkt @@ -3,10 +3,10 @@ ;;======== Dependencies =========== (require "../common.rkt") (require "../glue.rkt") +(require "../float.rkt") (require "../points.rkt") (require "../programs.rkt") (require "../alternative.rkt") -(require "../syntax/distributions.rkt") (require "../core/localize.rkt") (require "tools.rkt") @@ -26,8 +26,7 @@ ;; and a session-data object representing the state of their session. (define (start-session prog) (parameterize ([*start-prog* prog]) - (define samplers (map (curryr cons (eval-sampler 'default)) (program-variables prog))) - (define pcontext-extended (parameterize ([*num-points* 1024]) (prepare-points prog samplers 'TRUE))) + (define pcontext-extended (parameterize ([*num-points* 1024]) (prepare-points prog 'TRUE))) (define pcontext (random-subsample pcontext-extended 64)) (parameterize ([*pcontext* pcontext] [*analyze-context* pcontext]) (define alt (simplify-alt (make-alt prog))) diff --git a/src/web/tools.rkt b/src/web/tools.rkt index 5cc356e6f..86904f526 100644 --- a/src/web/tools.rkt +++ b/src/web/tools.rkt @@ -78,8 +78,8 @@ (for/list ([p (in-list pts)]) (let* ([exact-args (for/list ([arg (in-list (cdr subexpr))]) ((eval-exact `(λ ,vars ,arg)) p))] - [f-exact (real-op->bigfloat-op (car subexpr))] - [f-approx (real-op->float-op (car subexpr))] + [f-exact (operator-info (car subexpr) 'bf)] + [f-approx (operator-info (car subexpr) 'fl)] [exact (->flonum (apply f-exact exact-args))] [approx (apply f-approx (map ->flonum exact-args))] [local-err (add1 (abs (ulp-difference exact approx)))]) diff --git a/src/web/viz.rkt b/src/web/viz.rkt index c7c6cf4b3..c3e1770c3 100644 --- a/src/web/viz.rkt +++ b/src/web/viz.rkt @@ -1,7 +1,7 @@ #lang racket (require openssl/md5) -(require xml) +(require (rename-in xml [location? xml-location?])) (require web-server/servlet web-server/servlet-env web-server/dispatch web-server/page) (require web-server/configuration/responders) (require json) diff --git a/www/aec.html b/www/aec.html index 08f88fa83..92f7dc603 100644 --- a/www/aec.html +++ b/www/aec.html @@ -285,11 +285,4 @@

      Writing new tests

      A report is produced as usual.

      - diff --git a/www/doc/0.9/docker.html b/www/doc/0.9/docker.html index a8f4ec8ce..28a54a1da 100644 --- a/www/doc/0.9/docker.html +++ b/www/doc/0.9/docker.html @@ -65,12 +65,5 @@

      Installing the Herbie image

      on, located in dir.

      - diff --git a/www/doc/0.9/input.html b/www/doc/0.9/input.html index d032897fb..c81a719b2 100644 --- a/www/doc/0.9/input.html +++ b/www/doc/0.9/input.html @@ -127,12 +127,5 @@

      Distributions

      Both bounds are optional numeric constants. - diff --git a/www/doc/0.9/installing-herbgrind.html b/www/doc/0.9/installing-herbgrind.html index a0ec5b342..c94207264 100644 --- a/www/doc/0.9/installing-herbgrind.html +++ b/www/doc/0.9/installing-herbgrind.html @@ -93,12 +93,5 @@

      Installing HerbGrind

      check out the usage instructions.

      - diff --git a/www/doc/0.9/installing-herbie.html b/www/doc/0.9/installing-herbie.html index 645677dd4..1098a9369 100644 --- a/www/doc/0.9/installing-herbie.html +++ b/www/doc/0.9/installing-herbie.html @@ -89,12 +89,5 @@

      Installing Herbie

      check out the tutorial.

      - diff --git a/www/doc/0.9/options.html b/www/doc/0.9/options.html index 23fd4fa25..5801b1147 100644 --- a/www/doc/0.9/options.html +++ b/www/doc/0.9/options.html @@ -183,12 +183,5 @@

      Search Options

      and hypot) are available in your language. - diff --git a/www/doc/0.9/using-herbgrind.html b/www/doc/0.9/using-herbgrind.html index b513a76e6..193f605e4 100644 --- a/www/doc/0.9/using-herbgrind.html +++ b/www/doc/0.9/using-herbgrind.html @@ -126,12 +126,5 @@

      Turning HerbGrind on and off

      can be turned on and off multiple times.

      - diff --git a/www/doc/0.9/using-herbie.html b/www/doc/0.9/using-herbie.html index 69b9afb83..9289cce93 100644 --- a/www/doc/0.9/using-herbie.html +++ b/www/doc/0.9/using-herbie.html @@ -104,12 +104,5 @@

      Running Herbie

      Herbie's command-line options.

      - diff --git a/www/doc/1.0/docker.html b/www/doc/1.0/docker.html index 7dd12afbe..6b1be7121 100644 --- a/www/doc/1.0/docker.html +++ b/www/doc/1.0/docker.html @@ -65,12 +65,5 @@

      Installing the Herbie image

      on, located in dir.

      - diff --git a/www/doc/1.0/faq.html b/www/doc/1.0/faq.html index 2453def01..90c4fa82a 100644 --- a/www/doc/1.0/faq.html +++ b/www/doc/1.0/faq.html @@ -56,12 +56,5 @@

      “Cannot sample enough valid points” error

      parameter list.

      - diff --git a/www/doc/1.0/input.html b/www/doc/1.0/input.html index f8c9359c0..5f4ef5edc 100644 --- a/www/doc/1.0/input.html +++ b/www/doc/1.0/input.html @@ -156,12 +156,5 @@

      Distributions

      A uniform real value between a and b
      Both bounds must be numeric constants
      - diff --git a/www/doc/1.0/installing-herbgrind.html b/www/doc/1.0/installing-herbgrind.html index 5c5de5863..465c50f13 100644 --- a/www/doc/1.0/installing-herbgrind.html +++ b/www/doc/1.0/installing-herbgrind.html @@ -93,12 +93,5 @@

      Installing Herbgrind

      check out the usage instructions.

      - diff --git a/www/doc/1.0/installing-herbie.html b/www/doc/1.0/installing-herbie.html index 3a9a8f023..e6824764d 100644 --- a/www/doc/1.0/installing-herbie.html +++ b/www/doc/1.0/installing-herbie.html @@ -88,13 +88,6 @@

      Installing Herbie

      Once Herbie is installed and working correctly, check out the tutorial.

      - - + diff --git a/www/doc/1.0/options.html b/www/doc/1.0/options.html index 915051c06..2ff3b67df 100644 --- a/www/doc/1.0/options.html +++ b/www/doc/1.0/options.html @@ -196,12 +196,5 @@

      Search Options

      and hypot) are available in your language. - diff --git a/www/doc/1.0/release-notes.html b/www/doc/1.0/release-notes.html index f8d68fab1..625c573a5 100644 --- a/www/doc/1.0/release-notes.html +++ b/www/doc/1.0/release-notes.html @@ -116,13 +116,5 @@

      Try it out!

      If you find Herbie useful, let us know!

      - diff --git a/www/doc/1.0/using-herbgrind.html b/www/doc/1.0/using-herbgrind.html index 1acf14325..beeeca282 100644 --- a/www/doc/1.0/using-herbgrind.html +++ b/www/doc/1.0/using-herbgrind.html @@ -128,12 +128,5 @@

      Turning Herbgrind on and off

      can be turned on and off multiple times.

      - diff --git a/www/doc/1.0/using-herbie.html b/www/doc/1.0/using-herbie.html index 41e28ea5a..8dc1f4595 100644 --- a/www/doc/1.0/using-herbie.html +++ b/www/doc/1.0/using-herbie.html @@ -112,12 +112,5 @@

      Running Herbie

      Herbie's
      command-line options.

      - diff --git a/www/doc/1.1/docker.html b/www/doc/1.1/docker.html index c5770b9d4..3081d9b0c 100644 --- a/www/doc/1.1/docker.html +++ b/www/doc/1.1/docker.html @@ -118,12 +118,5 @@

      Running the web shell

      the examples above.

      - diff --git a/www/doc/1.1/faq.html b/www/doc/1.1/faq.html index bd02f10f2..ecd0b226e 100644 --- a/www/doc/1.1/faq.html +++ b/www/doc/1.1/faq.html @@ -16,29 +16,29 @@

      Frequently Asked Questions

      expressions into more accurate forms. This page catalogs questions frequently asked questions about Herbie.

      - +

      Troubleshooting common errors

      - +

      Several Herbie error messages refer to this page for additional information and debugging tips.

      - +

      “Invalid syntax” error

      This error means you mis-formatted Herbie's input. Common errors include misspelling function names and parenthesizing expressions - that must not be parenthesized. For example, in + that must not be parenthesized. For example, in (- (exp (x)) 1), (x) is incorrect: x is a variable so isn't parenthesized. (- (exp x) 1) would be the correct way of writing that expression. Please review the input format documentation for more.

      - +

      “Cannot sample enough valid points” error

      - +

      Herbie uses random sampling to select the points which it will use to evaluate the error of an expression. This error occurs when it @@ -47,7 +47,7 @@

      “Cannot sample enough valid points” error

      a valid result only when x is between -1001 and -999, a rather narrow range.

      - +

      The solution is to help out Herbie by specifying a precondition. Specify :pre (< -1001 x -999) for the example @@ -96,12 +96,5 @@

      “tcp-write: error writing” error with web shell

      which seems to be due to the Racket web server library.

      - diff --git a/www/doc/1.1/input.html b/www/doc/1.1/input.html index a610a2334..9eb3a4308 100644 --- a/www/doc/1.1/input.html +++ b/www/doc/1.1/input.html @@ -146,12 +146,5 @@

      Converting from Herbie 0.9

      racket infra/convert.rkt file.rkt > file.fpcore
      - diff --git a/www/doc/1.1/installing.html b/www/doc/1.1/installing.html index 9c3c89692..7915a707b 100644 --- a/www/doc/1.1/installing.html +++ b/www/doc/1.1/installing.html @@ -121,12 +121,5 @@

      Installing Herbie from Docker

      how to run Herbie with Docker.

      - diff --git a/www/doc/1.1/options.html b/www/doc/1.1/options.html index 266e45905..86f5e8dac 100644 --- a/www/doc/1.1/options.html +++ b/www/doc/1.1/options.html @@ -57,9 +57,9 @@

      Herbie commands

      produces of error versus input, which can help you understand whether Herbie's improvements matter for your user cases.

      -

      For any tool, you can run herbie tool --help to see - a listing of all available command-line options. This listing will - include unsupported options not listed on this page.

      +

      For any tool, you can run herbie tool --help + to see a listing of all available command-line options. This listing + will include unsupported options not listed on this page.

      Upgrading from Herbie 1.0

      @@ -84,9 +84,13 @@

      General options

      These options can be set on any tool. Pass them after the tool - name but before other arguments. + name but before other arguments, such as:

      +
      herbie improve --timeout 60 in.fpcore out.fpcore
      + +

      Arguments cannot be put anywhere else.

      +
      --seed S
      The random seed, which changes the randomly-selected points @@ -161,19 +165,20 @@

      Search options

      - Each option can be turned on with the -o X - or --disable X command-line flag, and turned off with - the +o X or --enable X. The recommended - set of options is the default set; turning a default-on option off - typically results in less-accurate results, while turning a - default-off option on typically results in more-complex and - more-surprising output expressions. + Each option can be turned off with the -o X + or --disable X command-line flag, and turned on with + the +o X or --enable X. The defaults are + the recommended options; turning a default-on option off typically + results in less-accurate results, while turning a default-off + option on typically results in more-complex and more-surprising + output expressions.

      precision:double
      -
      This option, on by default, runs Herbie in double-precision - mode. If turned off, Herbie runs in single-precision mode.
      +
      This option, on by default, tells Herbie to treat its input as + double-precision calculations. If turned off, Herbie treats its + input as a single-precision calculation.
      setup:simplify
      This option, on by default, simplifies the expression before @@ -216,10 +221,11 @@

      Search options

      reduce:regimes
      This option, on by default, uses Herbie's regime inference algorithm to branch between several program candidates. If - turned off, brances will be inferred and the output program will - be straight-line code (if the input was). You will want to turn - this option off if your programming environment makes branches - too expensive, such as in some cases of GPU programming.
      + turned off, branches will not be inferred and the output program + will be straight-line code (if the input was). You will want to + turn this option off if your programming environment makes + branches very expensive, such as in some cases of GPU + programming.
      reduce:taylor
      This option, on by default, uses a final set of series @@ -267,16 +273,18 @@

      Search options

      rules:exponents
      This option, on by default, allows Herbie to use facts about exponents during its search. If turned off, Herbie will not be - able to use those facts. You rarely want to turn this option off - if you do not want to use exponents or logarithms in the output - expressions, which might be the case when code runtime is more - important than accuracy.
      + able to use those facts. You may want to turn this option off in + the rare case that you do not want exponents or logarithms + used in Herbie's output expressions, which may be the case when + code runtime is more important than accuracy.
      rules:trigonometry
      This option, on by default, allows Herbie to use basic trigonometry facts during its search. If turned off, Herbie will - not be able to use those facts. Herbie's trigonometry knowledge - is extremely basic. You will rarely want to turn this option off.
      + not be able to use those facts. You may want to turn this option + off in the rare case that you do not want trigonometric + functions used in Herbie's output expressions, which may be the + case when code runtime is more important than accuracy.
      rules:numerics
      This option, off by default, allows Herbie to use special @@ -286,12 +294,5 @@

      Search options

      and hypot) are available in your language.
      - diff --git a/www/doc/1.1/release-notes.html b/www/doc/1.1/release-notes.html index 19b31d519..2c2caa0b9 100644 --- a/www/doc/1.1/release-notes.html +++ b/www/doc/1.1/release-notes.html @@ -4,7 +4,7 @@ Herbie 1.1 Release Notes - + @@ -56,17 +56,17 @@

      Breaking Changes and Deprecations

      Usability improvements

        -
      • HTML reports. Herbie can +
      • HTML reports. Herbie can now generate reports that show graphs, describe how Herbie achieved its result, and more.
      • -
      • Web shell. The +
      • Web shell. The old web demo has been cleaned up and made available to users, providing a graphical interface to Herbie.
      • -
      • Batch mode. It's now +
      • Batch mode. It's now simpler to run Herbie on an FPCore file and save the results in an output file. - The command-line shell + The command-line shell is also more user-friendly.
      • Easier installation using the Racket package manager.
      • @@ -136,13 +136,5 @@

        Try it out!

        If you find Herbie useful, let us know!

        - diff --git a/www/doc/1.1/report.html b/www/doc/1.1/report.html index 0024f8bee..6eb2f5d1a 100644 --- a/www/doc/1.1/report.html +++ b/www/doc/1.1/report.html @@ -115,12 +115,5 @@

        The Herbie report

        touch
        if there is more information you'd like to see.

        - diff --git a/www/doc/1.1/using-cli.html b/www/doc/1.1/using-cli.html index 98f4d526e..707ed67cc 100644 --- a/www/doc/1.1/using-cli.html +++ b/www/doc/1.1/using-cli.html @@ -120,12 +120,5 @@

        Batch processing FPCore

        Herbie's command-line options.

        - diff --git a/www/doc/1.1/using-web.html b/www/doc/1.1/using-web.html index cce276ce8..447836042 100644 --- a/www/doc/1.1/using-web.html +++ b/www/doc/1.1/using-web.html @@ -61,6 +61,7 @@

        The Herbie web shell

        Herbie improvement in progress.
        +

        The web shell will print Herbie's progress, and redirect to a report once Herbie is done. @@ -72,7 +73,6 @@

        The Herbie web shell

        options, including automatically saving the generated reports.

        -

        Batch report generation

        A report can also be generated directly @@ -134,13 +134,5 @@

        Input expressions

        You can check out our input format documentation for more about the Herbie input format.

        - - diff --git a/www/doc/1.2/compare.js b/www/doc/1.2/compare.js new file mode 100644 index 000000000..ac1862950 --- /dev/null +++ b/www/doc/1.2/compare.js @@ -0,0 +1,91 @@ +margin = 10; +barheight = 10; +width = 505; +textbar = 20; + +function sort_by(i1, i2) { + return function(a, b) { + return b[i1][i2] - a[i1][i2]; + } +} + +function r10(d) { + return "" + (Math.round(d * 10) / 10); +} + +function make_graph(node, data, start, end) { + var len = data.length; + var precision = 64; // TODO + + var a = d3.selectAll("script"); + var script = a[0][a[0].length - 1]; + + var svg = node + .attr("width", width + 2 * margin) + .attr("height", len * barheight + 2 * margin + textbar) + .append("g").attr("transform", "translate(" + margin + "," + margin + ")"); + + for (var i = 0; i <= precision; i += 4) { + svg.append("line") + .attr("class", "gridline") + .attr("x1", i / precision * width) + .attr("x2", i / precision * width) + .attr("y1", 0) + .attr("y2", len * barheight); + + svg.append("text").text(i) + .attr("x", i / precision * width) + .attr("width", 80) + .attr("y", len * barheight + textbar); + } + + var bar = svg.selectAll("g").data(data).enter(); + + function line_y(d, i) { return (i + .5) * barheight; } + function title(d, i) { return d.name + " (" + r10(precision - d["Old"][start]) + " to " + r10(precision - d["Old"][end]) + ")"; } + + bar.append("line") + .attr("class", "guide") + .attr("x1", 0) + .attr("x2", function(d) { return (precision - Math.max(d["Old"][start], d["Old"][end])) / precision * width }) + .attr("y1", line_y) + .attr("y2", line_y); + + var g = bar.append("g").attr("title", title); + + g.append("line").attr("class", "old") + .attr("x1", function(d) {return (precision - d["Old"][start]) / precision * width}) + .attr("x2", function(d) { return (precision - d["Old"][end]) / precision * width }) + .attr("y1", line_y) + .attr("y2", line_y); + + g.append("line").attr("class", "new") + .attr("x1", function(d) {return (precision - d["Old"][end]) / precision * width}) + .attr("x2", function(d) { return (precision - d["New"][end]) / precision * width }) + .attr("y1", line_y) + .attr("y2", line_y); + + g.append("g") + .attr("class", function(d) { return d["New"][end] < d["Old"][end] - .5 ? "new" : "old" }) + .attr("transform", function(d, i) { + return "translate(" + ((precision - d["New"][end]) / precision * width) + ", " + line_y(d, i) + ")"; + }) + .append("polygon").attr("points", "0,-3,0,3,5,0"); +} + +function draw_results(node) { + d3.json("results-old.json", function(err, old_data) { + d3.json("results-new.json", function(err, new_data) { + if (err) return console.error(err); + data = []; + old_data.tests.sort(function(a, b) { return (a.input < b.input) ? -1 : (a.input == b.input) ? 0 : 1}); + new_data.tests.sort(function(a, b) { return (a.input < b.input) ? -1 : (a.input == b.input) ? 0 : 1}); + for (var i = 0; i < old_data.tests.length; i++) { + data.push({Old: old_data.tests[i], New: new_data.tests[i]}); + } + + data.sort(sort_by("Old", "start")); + make_graph(node, data, "start", "end"); + }); + }); +} diff --git a/www/doc/1.2/docker.html b/www/doc/1.2/docker.html new file mode 100644 index 000000000..3081d9b0c --- /dev/null +++ b/www/doc/1.2/docker.html @@ -0,0 +1,122 @@ + + + + + Herbie on Docker + + + +
        + +

        Installing with Docker

        +
        + +

        + Herbie's is available + through Docker, which is a + sort of like an easily-scriptable virtual machine. This page + describes how to install + the official Docker + image for Herbie. +

        + +

        + Herbie can also be installed + normally. +

        + +

        Installing the Herbie image

        + +

        + First, install + Docker. Docker supports Windows, OS X, and Linux. Depending on + how you install Docker, you may need to prefix + all docker commands on this page + with sudo or run them as the root or administrative + user. +

        + +

        + With Docker installed, you should be able to download the Herbie image with: +

        + + +
        docker pull uwplse/herbie
        + +

        + You can now run Herbie: +

        + +
        docker run -it uwplse/herbie shell
        + +

        + This will run the Herbie shell, + reading input from the standard input. +

        + +

        Generating files and reports

        + +

        + To use Herbie in batch mode, you will + need to mount the input in the Docker container. Do that with: +

        + +
        $ docker run -it \
        +    -v in-dir:/in \
        +    -v out-dir:/out \
        +    -u $USER \
        +    uwplse/herbie improve /in/in-file /out/out-file
        + +

        + In this command, you are asking Herbie to read input + from in-file in in-dir, and write output + to out-file in out-dir. The command looks + the same if you want Herbie to read input from a directory; + just leave in-file blank. +

        + +

        + To generate reports from Herbie, you can run: +

        + +
        $ docker run -it \
        +    -v in-dir:/in \
        +    -v out-dir:/out \
        +    -u $USER \
        +    uwplse/herbie report /in/in-file /out/
        + +

        + As before, the input and output directories must be mounted inside + the Docker container. Note that both here and above, the user is + set to the current user. This is to ensure that the files Herbie creates + have the correct permissions set. +

        + +

        Running the web shell

        + +

        + Running the web shell in Docker requires exposing the ports inside + the container. Use the -p option to Docker to expose + whatever ports Herbie chooses to use, and then use + the --port option to Herbie to choose that port. +

        + +
        $ docker run -itp \
        +    uwplse/herbie web --quiet
        + +

        + Note that the --quiet flag is passed, + to prevent Herbie from attempting to start a web server + inside the Docker container. +

        + +

        + If you are using the --log + or --save-session flags for the web shell, + you will also need to mount the relevant directories into the + Docker container using the -v Docker option, as in + the examples above. +

        + + + diff --git a/www/doc/1.2/faq.html b/www/doc/1.2/faq.html new file mode 100644 index 000000000..71f8f0290 --- /dev/null +++ b/www/doc/1.2/faq.html @@ -0,0 +1,100 @@ + + + + + Herbie FAQ + + + +
        + +

        Frequently Asked Questions

        +
        + +

        + Herbie automatically transforms floating point + expressions into more accurate forms. This page catalogs questions + frequently asked questions about Herbie. +

        + +

        Troubleshooting common errors

        + +

        + Several Herbie error messages refer to this page for additional + information and debugging tips. +

        + +

        “Invalid syntax” error

        + +

        + This error means you mis-formatted Herbie's input. Common errors + include misspelling function names and parenthesizing expressions + that must not be parenthesized. For example, in + (- (exp (x)) 1), (x) is incorrect: + x is a variable so isn't parenthesized. (- (exp + x) 1) would be the correct way of writing that expression. + Please review the input format + documentation for more. +

        + +

        “Cannot sample enough valid points” error

        + +

        + Herbie uses random sampling to select the points which it will use + to evaluate the error of an expression. This error occurs when it + is not able to find enough valid points. For example, consider the + expression (acos (+ 1000 x)). This expression yields + a valid result only when x is between -1001 and -999, + a rather narrow range. +

        + +

        + The solution is to help out Herbie by specifying a precondition. + Specify :pre (< -1001 x -999) for the example + above. Herbie will use the precondition to improve its sampling + strategy. +

        + +

        “No valid values” error

        + +

        + This error indicates that your precondition excludes all possible + inputs. For example, the precondition (< 3 x 2) + excludes all inputs. Herbie raises this exception only when it can + determine that no inputs work. The solution is to fix the + precondition to allow some inputs. Note that sufficiently complex + unsatisfiable preconditions instead raise the error above. +

        + +

        “Exceeded MPFR precision limit” error

        + +

        + Herbie computes "ground truth" results using + MPFR. For some expressions, + like (sin (exp x)), using MPFR in this way requires + exponentially many bits to compute a correct result. Instead of + simply timing out in such cases, Herbie limits the MPFR precision + to 10,000 bits and raises this error when it hits that limit. +

        + +

        Missing reports chart on Chrome

        + +

        + When using Chrome to view web pages on your local machine, Chrome + disables certain APIs for security reasons; this prevents the + Herbie reports from drawing the chart. Run Chrome + with --allow-file-access-from-files to fix this error. +

        + +

        "Warning: native operation not supported on your system"

        + +

        + Some systems may not support a native implementation for all + operations that Herbie uses. Herbie provides a default fallback + implementation which is used by default for functions whose + native implementation is not found. You can disable this fallback + functionality with --disable precision:fallback. +

        + + + diff --git a/www/doc/1.2/input.html b/www/doc/1.2/input.html new file mode 100644 index 000000000..eae4c075a --- /dev/null +++ b/www/doc/1.2/input.html @@ -0,0 +1,206 @@ + + + + + Herbie Input Format + + + +
        + +

        The Input Format

        +
        + +

        + Herbie's input format is designed for + expressing mathematical functions, which Herbie can then search + for accurate implementations of. It also allows specifying the + distribution that Herbie draws inputs from when evaluating the + accuracy of an expression. +

        + +

        General format

        + +

        Herbie uses the FPCore format + for its input expression, which looks like this:

        + +
        (FPCore (inputs ...) properties ... expression)
        + +

        + Each input is a variable, like x, which can be used + in the expression, whose accuracy Herbie will try to improve. + Properties are described below. +

        + +

        + The expression is written in prefix form, with every function call + parenthesized, as in Lisp. For example, the formula for the + hypotenuse of a triangle with legs a and b is +

        + +
        (FPCore (a b) (sqrt (+ (* a a) (* b b))))
        + +

        + We recommend the .fpcore file extension for Herbie input files. +

        + +

        Supported functions

        + +

        + Herbie supports all functions + from math.h + with floating-point-only inputs and outputs. The best supported + functions, far from the full list, include: +

        + +
        +
        +, -, *, /, fabs
        +
        The usual arithmetic functions
        - is both negation and subtraction
        +
        sqrt, cbrt
        +
        Square and cube roots
        +
        pow, exp, log
        +
        Various exponentiations and logarithms
        +
        sin, cos, tan
        +
        The trigonometric functions
        +
        asin, acos, atan, atan2
        +
        The inverse trigonometric functions
        +
        sinh, cosh, tanh
        +
        The hyperbolic functions
        +
        asinh, acosh, atanh
        +
        The inverse hyperbolic functions
        +
        fma, expm1, log1p, hypot
        +
        Specialized numeric functions
        +
        + +

        Herbie also supports the constants PI and E.

        + +

        Herbie links against libm to ensure that every + function has the same behavior in Herbie as in your code. However, + on Windows platforms some functions are not available in the + system libm. In these cases Herbie will use a fallback + implementation and print a warning; turning off the + the precision:fallback option + disables those functions instead.

        + +

        Conditionals

        + +

        FPCore uses if for conditional expressions:

        + +
        (if cond if-true if-false)
        + +

        + An if epxression evaluates the + conditional cond and returns either if-true if + it is true or if-false if it is not. Conditionals may use: +

        + +
        +
        ==, !=, <, >, <=, >=
        +
        The usual comparison operators
        +
        and, or, not
        +
        The usual logical operators
        +
        TRUE, FALSE
        +
        The two boolean values
        +
        + +

        Note that unlike the arithmetic operators, the comparison functions can take any number of arguments.

        + +

        Intermediate variables

        + +

        Intermediate variables can be defined using let:

        + +
        (let ([variable value] ...) body)
        + +

        + In a let expression, all the values are evaluated + first, and then are bound to their variables in the body. This + means that the value of one variable can't refer to another + variable in the same let block; nest let + constructs if you want to do that. +

        + +

        + Note that Herbie treats intermediate values only as a notational + convenience, and inlines their values before improving the + formula's accuracy. Using intermediate variables will not help + Herbie improve a formula's accuracy or speed up its run-time. +

        + +

        Complex Numbersβ

        + +

        Herbie includes experimental support for complex numbers; + however, this support is currently limited to the basic arithmetic + operations. Some of Herbie's internal mechanisms for improving + expression accuracy also do not yet support complex-number + expressions.

        + +

        All input parameters are real numbers; complex numbers must be + constructed with complex. The + functions +, -, *, /, re, im, + and conj are available on complex numbers. Note that + complex and real operations use the same syntax; however, complex + and real arithmetic cannot be mixed: (+ (complex 1 2) + 1) is not valid. A type checker will report such errors.

        + +

        Complex operations use + the Racket + implementation, so results may differ (slightly) for the complex + library used in your language, especially for non-finite complex + numbers. Unfortunately, complex number arithmetic is not as + standardized as float-point arithmetic.

        + +

        In the future, we hope to support complex-number arguments and + fully support all complex-number operations.

        + +

        Properties

        + +

        Herbie also allows several FPCore properties specified on inputs for additional meta-data:

        + +
        +
        :name string
        +
        Herbie uses this name in its output
        +
        :pre test
        +
        Herbie samples only points that pass the test in the reals
        +
        +
        + +

        + Several additional properties can be found in the benchmark suite + and are used for testing, but are not supported and can change + without warning. +

        + +

        Herbie's output uses custom FPCore properties in its output to + provide meta-data about the Herbie improvement process:

        + +
        +
        :herbie-status status
        +
        Describes whether Herbie successfully improved the accuracy of the input; status is one of success, timeout, error, or crash.
        +
        :herbie-time ms
        +
        The time, in milliseconds, used by Herbie to find a more accurate formula.
        +
        :herbie-bits-used bits
        +
        The precision used to find accurate outputs from the formula.
        +
        :herbie-error-input ([pts err] ...)
        +
        The computed average error of the input program, evaluated on pts points. Multiple entries correspond to multiple training or test sets.
        +
        :herbie-error-output ([pts2 err1] [pts2 err2])
        +
        The computed average error of the output program, like above.
        +
        + +

        Herbie's output also passes through any :name + and :pre properties on its inputs.

        + +

        Converting from Herbie 0.9

        + +

        + Herbie 0.9 used a different input + format, which is not supported Herbie 1.0 and later. To + simplify the transition, the infra/convert.rkt script + converts from the old to the new format. +

        + +

        To use the conversion tool, run:

        + +
        racket infra/convert.rkt file.rkt > file.fpcore
        + + + diff --git a/www/doc/1.2/installing.html b/www/doc/1.2/installing.html new file mode 100644 index 000000000..8f8c9b8dc --- /dev/null +++ b/www/doc/1.2/installing.html @@ -0,0 +1,127 @@ + + + + + Installing Herbie + + + +
        + +

        Installing Herbie

        +
        + +

        + Herbie currently supports Linux, OS X, and + Windowsβ. +

        + +

        + Herbie can be installed from a package or + from source. (It is also available in + a Docker image.) To install Herbie, first install + Racket, which Herbie is + written in. +

        + +

        Installing Racket

        + +

        + Use the official + installer to install Racket, or use distro-provided packages + provided they are version 6.7 or later of Racket (earlier versions + are not supported). +

        + +

        + Test that Racket is installed correctly and has a correct version: +

        + +
        $ racket
        +Welcome to Racket v6.12.
        +> (exit)
        + +

        Installing Herbie from a package

        + +

        Once Racket is installed, install Herbie with:

        + +
        raco pkg install herbie
        + +

        + This will install Herbie, compile it for faster startup, and place + an executable in your Racket user path, likely + into ~/.racket/6.12/. If you add this directory to + your PATH you will be able to run herbie with + the herbie command. +

        + +

        Installing Herbie from source

        + +

        + Once Racket is installed, download the Herbie source + from GitHub with: +

        + +
        git clone https://github.com/uwplse/herbie
        + +

        + If you go to the herbie directory, + you should see a README.md file, a directory named src, + a directory named bench/, and a few other directories. + Do a trial run of Herbie to make sure everything is installed and working correctly: +

        + +
        racket src/herbie.rkt report bench/tutorial.rkt graphs/
        + +

        + This command will take approximately a minute to run. + After the command completes, + a directory named graphs should be created. + Open the report.html file inside with your browser; + you will see a listing of the expressions Herbie was run on, + all of which should be green. + If not, please check that your Racket installation is at least version 6.7, + and if the error still persists, + please submit a bug. +

        + +

        You can make Herbie start up faster by byte-compiling it:

        + +
        raco make src/herbie.rkt
        + +

        + Once Herbie is installed and working correctly, + check out the tutorial. +

        + +

        Installing Herbie from Docker

        + +

        + Docker is a container manager, + which is sort of like an easily-scriptable virtual machine. We do + not recommend using Herbie through Docker without prior Docker + experience. +

        + +

        + First, install + Docker. Docker supports Windows, OS X, and Linux. Depending on + how you install Docker, you may need to prefix + all docker commands on this page + with sudo or run them as the root or administrative + user. +

        + +

        + With Docker installed, you should be able to download the Herbie image with: +

        + +
        docker pull uwplse/herbie
        + +

        + Check out the Docker page for more on + how to run Herbie with Docker. +

        + + + diff --git a/www/doc/1.2/options.html b/www/doc/1.2/options.html new file mode 100644 index 000000000..76ae6a248 --- /dev/null +++ b/www/doc/1.2/options.html @@ -0,0 +1,310 @@ + + + + + Herbie Command-line Options + + + +
        + +

        Command-line Options

        +
        + +

        + The herbie command has several + subcommands and allows multiple options that influence its search + procedure and the types of solutions it finds. These options apply + both to the report generator and the one-off command-line tool. +

        + +

        Herbie commands

        + +

        Herbie can be run both interactively and in batch mode, and can + generate output intended either for the command line or the web. We + call these different ways of running Herbie different tools. Herbie + provides four tools:

        + +
        +
        herbie web
        +
        Use Herbie through your browser. herbie web + starts a web server for running Herbie on your local machine, and + directs a browser to visit that server.
        + +
        herbie shell
        +
        Starts a command-line interactive shell for using Herbie. + Enter an FPCore expression and Herbie + will print its more-accurate version.
        + +
        herbie improve input output
        +
        Runs Herbie on the expressions in the input file + or directory, and outputs the result to output, which + will be a single file of FPCore outputs.
        + +
        herbie report input output
        +
        Runs Herbie on the expressions in the input file + or directory, and produces a directory of HTML web pages that + describe Herbie's output, how it derived that output, and + additional charts and information about the improvement process. + These pages can be viewed in any browser (though with + a quirk for Chrome).
        +
        + +

        We recommend using the web tools, web + and report, since HTML allows Herbie to give you more + information about how and why it improved a floating-point + expression's accuracy. Particularly useful are the graphs it + produces of error versus input, which can help you understand + whether Herbie's improvements matter for your user cases.

        + +

        For any tool, you can run herbie tool --help + to see a listing of all available command-line options. This listing + will include unsupported options not listed on this page.

        + +

        General options

        + +

        + These options can be set on any tool. Pass them after the tool + name but before other arguments, such as: +

        + +
        herbie improve --timeout 60 in.fpcore out.fpcore
        + +

        Arguments cannot be put anywhere else.

        + +
        +
        --seed S
        +
        The random seed, which changes the randomly-selected points + that Herbie evaluates candidate expressions on. The seed is a + number between 0 and 231 (exclusive both ends). This + option can be used to make Herbie's results reproducible or to + compare two different runs. Prior versions of Herbie used a + different format for seeds, which is also still supported.
        + +
        --num-iters N
        +
        The number of improvements Herbie attempts to make to the + program. The default, 4, suffices for most programs and helps + keep Herbie fast. If this is set very high, Herbie may run out + of things to do and terminate before the given number of + iterations, but in practice iterations beyond the first few + rarely lead to lower error. This option can be increased to 5 or + higher to check that there aren't further improvements that Herbie + could seek out.
        + +
        --num-points N
        +
        The number of randomly-selected points used to evaluate + candidate expressions. The default, 256, gives good behavior for + most programs. The more points sampled, the slower Herbie is. + This option can be increased to 512 or 1024 if Herbie gives very + inconsistent results between runs with different seeds.
        + +
        --timeout T
        +
        The timeout to use per-example, in seconds. A fractional + number of seconds can be given.
        + +
        --threads N, for improve and reports
        +
        Enables multi-threaded operation. By default, no threads are + used. A number can be passed to this option to use that many + threads, or yes can be passed to tell Herbie to use + all but one of the hardware threads.
        +
        + +

        Web shell options

        + +

        The web tool runs Herbie as a web server, and + connects to it from your browser. It has additional options to + control this server.

        + +
        +
        --port N
        +
        The port to run the Herbie server on. The default port is 8000.
        + +
        --save-session dir
        +
        Save all the reports for expressions enterred into the web + shell to this directory. The directory is also used as a + cache of already-computed expressions.
        + +
        --log file
        +
        Write a web access log to this file. The file is formatted + similarly to Apache logs. If Herbie crashes for some reason, this + log will not contain a traceback.
        + +
        --quiet
        +
        When set, a browser is not started to point to the server main + page, and a smaller banner is printed to the command line.
        +
        + +

        Rulesets

        + +

        + Herbie uses a set of rewrite rules to define the changes it is + allowed to make to formulas to improve their accuracy. These rules + can be turned on and off in groups using --disable + rules:group and --enable + rules:group. In general, enabling more rules + should only improve the accuracy of Herbie's output. However, if + certain functions are not available on your platform, disabling + the rules associated with those functions will prevent Herbie from + using them. +

        + +

        The full list of rule groups is:

        + + + + + + + + + + + + +
        Rule GroupTopic of rewrite rules
        arithmeticBasic arithmetic facts
        polynomialsFactoring and powers
        fractionsFraction arithmetic
        exponentsExponentiation identities
        trigonometryTrigonometric identities
        hyperbolicHyperbolic trigonometric identities
        specialSpecial mathematical functions
        complexComplex number arithmetic
        numericsSpecial numerical functions expm1, log1p, fma, and hypot
        + +

        All groups except numerics are enabled by default, + and we recommend turning it on if these functions are available in + your language. If complex arithmetic or special mathematical + functions are poorly implemented in your language, you may wish to + disable those rule groups as well.

        + +

        Search options

        + +

        + These options influence the fine properties of Herbie's search, most + importantly the types of transformations that Herbie uses to find + candidate programs. These options offer very fine-grained control + over Herbie's output, and are only recommended for advanced uses of + Herbie. +

        + +

        + Each option can be turned off with the -o X + or --disable X command-line flag, and turned on with + the +o X or --enable X. The defaults are + the recommended options; turning a default-on option off typically + results in less-accurate results, while turning a default-off + option on typically results in more-complex and more-surprising + output expressions. +

        + +
        +
        precision:double
        +
        This option, on by default, tells Herbie to treat its input as + double-precision calculations. If turned off, Herbie treats its + input as a single-precision calculation.
        + +
        precision:fallback
        +
        This option, on by default, tells Herbie to use fallback + functions if a native implementation is not found for any + operations. If turned off, operations with no native + implementation will be disabled from use in the input or output. + You will want to turn this option off if you are concerned with + the specific behavior of libm functions.
        + +
        setup:simplify
        +
        This option, on by default, simplifies the expression before + passing it to Herbie. If turned off, Herbie will not simplify + input programs before improving them. You will want to turn off + this option if simplifying the program will create a lot of + error, say if the association of operations is cleverly + chosen.
        + +
        setup:early-exit
        +
        This option, off by default, causes Herbie to exit without + modifying the input program if it determines that the input + program has less than 0.1 bits of error. You will want to turn + this option on if you are running Herbie on a large corpus of + programs that you do not believe to be inaccurate.
        + +
        generate:rr
        +
        This option, on by default, uses Herbie's recursive rewriting + algorithm to generate candidate programs. If turned off, Herbie + will use a non-recursive rewriting algorithm, which will + substantially limit the candidates Herbie finds. You will rarely + want to turn this option off.
        + +
        generate:taylor
        +
        This option, on by default, uses series expansion to produce + new candidates during the main improvement loop. If turned off, + Herbie will not use series expansion in the main improvement loop. + You will want to turn this option off if you want to avoid + series-expansion-based rewrites, such as if you need to preserve + the equivalence of the input and output expressions as real-number + formulas.
        + +
        generate:simplify
        +
        This option, on by default, simplifies candidates during the + main improvement loop. If turned off, candidates will not be + simplified, which typically results in much less accurate + expressions, since simplification is often necessary for + cancelling terms. You will rarely want to turn this option off.
        + +
        reduce:regimes
        +
        This option, on by default, uses Herbie's regime inference + algorithm to branch between several program candidates. If + turned off, branches will not be inferred and the output program + will be straight-line code (if the input was). You will want to + turn this option off if your programming environment makes + branches very expensive, such as in some cases of GPU + programming.
        + +
        reduce:simplify
        +
        This option, on by default, uses a final simplification pass + after all improvements have been made. This sometimes improves + accuracy further. If turned off, this final simplification pass + will not be done. You will rarely want to turn this option + off.
        + +
        reduce:avg-error
        +
        This option, on by default, causes Herbie to output the + candidate with the best average error over the chosen inputs. If + turned off, Herbie will choose the candidate with the least + maximum error instead. This usually produces programs with worse + overall accuracy. You may want to turn this option off if + worst-case accuracy is more important to you than overall + accuracy.
        + +
        reduce:binary-search
        +
        This option, on by default, uses binary search to refine the + values used in if statement conditionals. This + makes different runs of Herbie produce more similar results, and + improves accuracy near those values. If turned off, binary + search will not be used, and the branch values will be less + accurately chosen. You will want to turn this option off if + behavior near branches is not important to you, in which case + turning off this option will make Herbie slightly faster.
        + +
        reduce:branch-expressions
        +
        This option, on by default, allows Herbie to branch on + expressions, not just variables. This can improve accuracy on + regime branching, but can significantly increase the runtime, + particularly for large programs. If turned off, Herbie will only + try to branch on variables. You may want to turn this option off + if Herbie runtime is more important to you than expression + accuracy.
        +
        + +

        Upgrading from Herbie 1.0

        + +

        Herbie 1.0 used + a different command line + syntax, without multiple tools. Translate like so:

        + +
          +
        • herbie-1.0herbie-1.1 shell
        • +
        • herbie-1.0 fileherbie-1.1 improve file -
        • +
        • herbie-1.0 files ...cat files ... | herbie-1.1 improve - -
          + Alternatively, collect the files into a directory and run herbie-1.1 improve dir/ -
        • +
        + +

        The new syntax somewhat changes Herbie's behavior, such as by + using the input expression as the output if Herbie times out. It + also makes it easier to write Herbie's output to a file without + using command-line redirection. The old syntax still works but is + deprecated and will be removed in the next release.

        + + + + diff --git a/www/doc/1.2/release-notes.html b/www/doc/1.2/release-notes.html new file mode 100644 index 000000000..4533049da --- /dev/null +++ b/www/doc/1.2/release-notes.html @@ -0,0 +1,167 @@ + + + + + Herbie 1.2 Release Notes + + + + + +
        + +

        Herbie 1.2 Release Notes

        +
        + +

        + The Herbie developers are excited to announce + Herbie 1.2! This release focuses on accuracy and + reliability, including better conditionals, more accurate + defaults, and significant bug fixes to the core algorithms. +

        + +

        + Herbie automatically improves the accuracy of floating point + expressions. This avoids the bugs, errors, and surprises that so + often occur when working with floating point. Since + our PLDI'15 paper, we've been hard at + work making Herbie more versatile and easier to use. +

        + + + +

        Breaking Changes and Deprecations

        + +
          +
        • This release fixes a significant and important bug in + Herbie's measurement of program accuracy. Herbie's prior results + had a small chance of recommending an inaccurate program as + accurate.
        • +
        • In line + with FPCore + 1.0, we have deprecated the sqr + and cube functions.
        • +
        • Herbie no longer + supports Racket versions + prior to 6.7. Future Herbie releases may continue to step up + supported Racket versions to make better use of recent language + features.
        • +
        + +

        Improvement to core algorithm

        + +
          +
        • Herbie now uses a more rigorous algorithm to evaluate its results, + both increasing reproducibility of its results and better measuring + its output.
        • +
        • Herbie has become much more inventive in what expressions it + can branch on. This leads to more accurate results in many + cases. The reduce:branch-expressions + option controls this feature.
        • +
        • Herbie now uses a binary search algorithm to choose more + accurate values for conditionals in if statements. + This should make different runs of Herbie produce more similar + results. The reduce:binary-search + option controls this feature.
        • +
        • Herbie has a higher default value for + the --num-iters parameter. + Users should expect Herbie to be slower but to produce more + accurate results.
        • +
        • A significant bug in the series expansion algorithm has been + fixed, improving Herbie's performance in the presence of + logarithms.
        • +
        • A small tweak to the simplification algorithm results in + simpler and more accurate output from Herbie.
        • +
        + +

        Beta-quality features

        + +
          +
        • Herbie now supports basic operations + on complex numbers, using + the complex, re, and im + functions. We look forward to releasing high-quality complex + number support in the future.
        • +
        • Herbie now supports Windows. Note that the Bessel functions + are not available in the Windows math.h library and + use a fallback. The precision:fallback + option controls this feature.
        • +
        + +
        + +
        \[c0 \cdot \sqrt{\frac{A}{V \cdot \ell}}\]
        +
        +
        \[\begin{array}{l} +\mathbf{if}\;\frac{1}{V \cdot \ell} \le -3.767671897931721 \cdot 10^{+27}:\\ +\;\;\;\;\frac{c0 \cdot \sqrt{1}}{\sqrt{\frac{V \cdot \ell}{A}}}\\ + +\mathbf{elif}\;\frac{1}{V \cdot \ell} \le -2.9824307461679933 \cdot 10^{-248}:\\ +\;\;\;\;\left(c0 \cdot \sqrt{\sqrt{\frac{A}{V \cdot \ell}}}\right) \cdot \sqrt{\sqrt{\frac{A}{V \cdot \ell}}}\\ + +\mathbf{elif}\;\frac{1}{V \cdot \ell} \le 7.59312080698644 \cdot 10^{-301}:\\ +\;\;\;\;c0 \cdot \sqrt{\frac{\frac{A}{V}}{\ell}}\\ + +\mathbf{else}:\\ +\;\;\;\;c0 \cdot \frac{\sqrt{A}}{\sqrt{V \cdot \ell}}\\ + +\end{array}\]
        +
        A program produced by the new, more create branch + inference system in Herbie 1.2. Herbie 1.2 is more creative and + produces more accurate output than prior versions.
        +
        + +

        Usability improvements

        + +
          +
        • A new Try It feature in + reports lets you run the input program and Herbie's suggested + version on argument values of your choice.
        • +
        • Herbie can now efficiently sample from preconditions such + as (or (< 1 x 2) (< 1001 x 1002)). Previously + such preconditions would produce to the dreaded + “could not sample” + error message.
        • +
        • Herbie's web output now includes additional descriptive text, + such as color keys, and additional intuitive interactions, such + as clicking on report page arrows.
        • +
        • Herbie's FPCore output now includes + its error estimates, making + this information easier for other tools to access.
        • +
        • let statements and variary arithmetic operators + are now supported in preconditions.
        • +
        • Herbie will now type-check inputs and report errors for + mismatches, helping further cut down on confusing error + messages.
        • +
        • User errors and Herbie crashes now look different in + reports.
        • +
        + +

        Code Cleanup

        + +
          +
        • Many bugs fixed, including adding missing rules, infinite + loops, and a few crashes in exceptional circumstances.
        • +
        • Herbie’s HTML output now uses the Racket XML library, + eliminating the possibility of generating invalid HTML.
        • +
        • Herbie uses a new mechanism for defining supported functions, + which should adding functions in the future easier.
        • +
        + +

        Try it out!

        + +

        + We're excited to continue to improve Herbie and make it more + useful to scientists, engineers, and programmers around the world. + We've got a lot of features we're excited to work on in the coming + months. Please + report bugs, + join + the + mailing list, + or contribute. +

        + +

        If you find Herbie useful, let us know!

        + + diff --git a/www/doc/1.2/report-derivation.png b/www/doc/1.2/report-derivation.png new file mode 100644 index 000000000..aaf113483 Binary files /dev/null and b/www/doc/1.2/report-derivation.png differ diff --git a/www/doc/1.2/report-error.png b/www/doc/1.2/report-error.png new file mode 100644 index 000000000..2b4043984 Binary files /dev/null and b/www/doc/1.2/report-error.png differ diff --git a/www/doc/1.2/report-large.png b/www/doc/1.2/report-large.png new file mode 100644 index 000000000..6c4b03fb4 Binary files /dev/null and b/www/doc/1.2/report-large.png differ diff --git a/www/doc/1.2/report-prog.png b/www/doc/1.2/report-prog.png new file mode 100644 index 000000000..67d7dc9aa Binary files /dev/null and b/www/doc/1.2/report-prog.png differ diff --git a/www/doc/1.2/report-runtime.png b/www/doc/1.2/report-runtime.png new file mode 100644 index 000000000..899dceff1 Binary files /dev/null and b/www/doc/1.2/report-runtime.png differ diff --git a/www/doc/1.2/report-try-it.png b/www/doc/1.2/report-try-it.png new file mode 100644 index 000000000..8b2be6d47 Binary files /dev/null and b/www/doc/1.2/report-try-it.png differ diff --git a/www/doc/1.2/report.html b/www/doc/1.2/report.html new file mode 100644 index 000000000..1643801b9 --- /dev/null +++ b/www/doc/1.2/report.html @@ -0,0 +1,144 @@ + + + + + Herbie reports + + + +
        + +

        Herbie reports

        +
        + + +

        The Herbie report

        + +

        The Herbie report, which is output by + the Herbie web commands, lists five items.

        + +

        Summary numbers

        + +

        First, a brief summary of the results. + For most uses, the “Average Error” + number, which summarizes how accurate the input and output + expressions are, is the most important number in this section. + The other numbers are: + the time Herbie took to improve the program; + the precision Herbie assumed floating-point operations + (which can be set at the command line); + and the internal precision used to ensure accurate results.

        + +
        + +
        A summary of results from a Herbie report.
        +
        + +

        Input and output programs

        + +

        Second, the input and output programs themselves. + These are printed in standard mathematical syntax. + Library functions not often used by mathematicians, + including atan2, expm1, + fma, hypot, lgamma, + log1p, and logb + are drawn with a sub- or super-script asterisk, + while if statements are rendered as in a program. +

        + +
        + +
        Input and output program from a Herbie report.
        +
        + +

        Error graph

        + +

        + Third, under Error, a graph of floating-point error + versus input value. This is helpful for understanding the sorts of + inputs Herbie is improving accuracy on. Sometimes, Herbie improved + accuracy on some inputs at the cost of accuracy on other inputs + that you care more about. In these cases, you can add + a :precondition to restrict the inputs Herbie reasons + about. +

        + +

        + On these graphs, the red line is the error of the input program, + while the blue line is the error of the output program + (both can be toggled). + For expressions with multiple variables, + the variable on the horizontal axis can be selected. + If Herbie decided to insert + an if statement into the program, + the locations of those if statements + will be marked with vertical bars. +

        + +
        + +
        An error graph from a Herbie report. Note the variable + selector (x is selected) and the toggles for the + input and output program (both are toggled on).
        +
        + +

        Try it

        + +

        + Fourth, a form where you can try out specific inputs on the input + program and Herbie's output program. Enter the argument values on + the left, and the input and output programs will be evaulated on + those arguments and the results printed on the right. +

        + +
        + +
        + Try it out section on a simple program. +
        +
        + +

        Derivation

        + +

        Fifth, a derivation of the output from the input. + For complex or unexpected programs, these can be helpful. + Each substantive step in the derivation also lists the error, + in bits, of that step's output.

        + +

        The derivations may name rules built into Herbie, + or may claim derivation steps are done by simplification, + series expansion, or other Herbie strategies. The derivation will + also call out splits of the input into regimes, and strategies + Herbie is invoking. When one part of the term is colored blue, + that is the only part of the term modified by the operation. +

        + +
        + +
        A short derivation from a Herbie report. Note the + error at each step, in bits, in gray.
        +
        + +

        Runtime information

        + +

        Sixth and finally, a breakdown of Herbie's runtime. + This can usually be ignored. + The colored bar is a timeline of Herbie's run, + with each section of the bar sized proportionally to its runtime, + and each color corresponding to a strategy; + hover over that section of the bar to learn which strategy.

        + +

        If you find a bug, include the code snippet in this section when + filing the bug. Please also include the debug log linked from this block.

        + +
        + +
        Runtime breakdown from a Herbie report.
        +
        + +

        We expect the report to grow more informative with future + versions. Please get in + touch if there is more information you'd like to see.

        + + + diff --git a/www/doc/1.2/results-new.json b/www/doc/1.2/results-new.json new file mode 100644 index 000000000..24fd81d3c --- /dev/null +++ b/www/doc/1.2/results-new.json @@ -0,0 +1 @@ +{"flags":["precision:double","setup:simplify","reduce:regimes","reduce:taylor","reduce:simplify","reduce:avg-error","rules:arithmetic","rules:polynomials","rules:fractions","rules:exponents","rules:trigonometry","rules:hyperbolic","generate:rr","generate:taylor","generate:simplify"],"seed":"#(772101555 1905824529 294602591 2478279198 2123125427 4197813737)","points":256,"date":1493418730,"commit":"a6770931126e0702f83b80fffb3cdf362d9e07c9","branch":"develop","iterations":3,"note":false,"bit_width":64,"tests":[{"bits":1408,"start":39.78563602870575,"input":"(sqrt (/ (- (exp (* 2 x)) 1) (- (exp x) 1)))","output":"(sqrt (/ (+ (exp x) 1) 1))","link":"0-sqrtexpproblem344","ninf":0,"pinf":0,"end-est":0.0078125,"name":"sqrtexp (problem 3.4.4)","samplers":["default"],"time":53697.2470703125,"status":"imp-start","vars":["x"],"target":false,"end":0.014412722522414014},{"bits":2432,"start":31.277522201292555,"input":"(/ (- x (sin x)) (- x (tan x)))","output":"(if (<= x -9.950485992669078e-09) (- (/ x (- x (tan x))) (/ (sin x) (- x (tan x)))) (if (<= x 0.16631490308113306) (- (* 9/40 (sqr x)) (+ (* 27/2800 (pow x 4)) 1/2)) (/ (- x (sin x)) (- x (tan x)))))","link":"1-sintanproblem345","ninf":0,"pinf":0,"end-est":0.36733237039018785,"name":"sintan (problem 3.4.5)","samplers":["default"],"time":135208.11889648438,"status":"imp-start","vars":["x"],"target":false,"end":0.1040637469913252},{"bits":2432,"start":36.53944527020705,"input":"(/ (+ (- b/2) (sqrt (- (sqr b/2) (* a c)))) a)","output":"(if (<= b/2 -1.751131060884064e+136) (* -2 (/ b/2 a)) (if (<= b/2 8.548826144111727e-60) (/ 1 (/ a (+ (- b/2) (sqrt (- (sqr b/2) (* a c)))))) (- (/ (+ b/2 (- b/2)) a) (/ (* 1/2 c) b/2))))","link":"2-quad2pproblem321positive","ninf":0,"pinf":0,"end-est":8.759031935807828,"name":"quad2p (problem 3.2.1, positive)","samplers":["default","default","default"],"time":119061.97412109375,"status":"imp-start","vars":["a","b/2","c"],"target":false,"end":5.966762651991987},{"bits":2944,"start":37.82838784287472,"input":"(/ (- (- b/2) (sqrt (- (sqr b/2) (* a c)))) a)","output":"(if (<= b/2 -3.093544874321455e-77) (* (/ -1/2 b/2) c) (if (<= b/2 5.845042913155354e+61) (/ 1 (/ a (- (- b/2) (sqrt (- (sqr b/2) (* a c)))))) (+ (* (/ c b/2) 1/2) (/ (- (- b/2) b/2) a))))","link":"3-quad2mproblem321negative","ninf":0,"pinf":0,"end-est":8.241281491524308,"name":"quad2m (problem 3.2.1, negative)","samplers":["default","default","default"],"time":126471.29907226562,"status":"imp-start","vars":["a","b/2","c"],"target":false,"end":5.326392364138352},{"bits":2432,"start":31.059705602958424,"input":"(/ (- 1 (cos x)) (sqr x))","output":"(* (/ (sin x) x) (/ (/ (sin x) (+ 1 (cos x))) x))","link":"4-cos2problem341","ninf":0,"pinf":0,"end-est":0.3600447888363383,"name":"cos2 (problem 3.4.1)","samplers":["default"],"time":82701.72485351562,"status":"imp-start","vars":["x"],"target":false,"end":0.27141353358701925},{"bits":1408,"start":31.55214583076247,"input":"(- (pow (+ x 1) (/ 1 n)) (pow x (/ 1 n)))","output":"(if (<= n -2.672258838309128e-11) (- (- (/ (/ 1 x) n) (/ (/ 1/2 n) (sqr x))) (/ (log x) (* n (* n x)))) (if (<= n 19699878403.887928) (exp (cube (cbrt (log (- (pow (+ x 1) (/ 1 n)) (pow x (/ 1 n))))))) (- (- (/ (/ 1 x) n) (/ (/ 1/2 n) (sqr x))) (/ (log x) (* n (* n x))))))","link":"5-2nthrtproblem346","ninf":0,"pinf":0,"end-est":21.21301382692941,"name":"2nthrt (problem 3.4.6)","samplers":["default","default"],"time":143232.169921875,"status":"imp-start","vars":["x","n"],"target":false,"end":6.774720844192645},{"bits":1408,"start":40.755841804999065,"input":"(- (log (+ N 1)) (log N))","output":"(if (<= N 9328.390986348908) (log (/ (+ N 1) N)) (+ (/ (- (/ 1/3 N) 1/2) (sqr N)) (/ 1 N)))","link":"6-2logproblem336","ninf":0,"pinf":0,"end-est":0.11448705279133702,"name":"2log (problem 3.3.6)","samplers":["default"],"time":41444.029052734375,"status":"imp-start","vars":["N"],"target":false,"end":19.49352325492048},{"bits":896,"start":14.049500988425633,"input":"(- (/ 1 (+ x 1)) (/ 1 x))","output":"(if (<= x -209135036385.79047) (- (/ 1 (pow x 3)) (+ (pow x (- 2)) (/ 1 (pow x 4)))) (if (<= x 290639.63932394225) (/ (- x (+ 1 x)) (* (+ x 1) x)) (- (/ 1 (pow x 3)) (+ (pow x (- 2)) (/ 1 (pow x 4))))))","link":"7-2fracproblem331","ninf":0,"pinf":0,"end-est":0.0234375,"name":"2frac (problem 3.3.1)","samplers":["default"],"time":30894.91796875,"status":"imp-start","vars":["x"],"target":false,"end":0.014198120312590145},{"bits":2432,"start":38.890098631337246,"input":"(- (cos (+ x eps)) (cos x))","output":"(if (<= eps -3.645937152382937e+19) (- (- (* (cos x) (cos eps)) (* (sin x) (sin eps))) (cos x)) (if (<= eps 4.729663737457019e-05) (* -2 (* (sin (/ eps 2)) (sin (/ (+ (+ x eps) x) 2)))) (- (* (cos x) (cos eps)) (+ (* (sin x) (sin eps)) (cos x)))))","link":"8-2cosproblem335","ninf":0,"pinf":0,"end-est":0.6303043448114194,"name":"2cos (problem 3.3.5)","samplers":["default","default"],"time":104279.31079101562,"status":"imp-start","vars":["x","eps"],"target":false,"end":1.2578573335845715},{"bits":1408,"start":29.805220676286638,"input":"(- (pow (+ x 1) (/ 1 3)) (pow x (/ 1 3)))","output":"(/ 1 (+ (sqr (pow (+ x 1) (/ 1 3))) (+ (sqr (exp (/ (log x) 3))) (* (pow (+ x 1) (/ 1 3)) (pow x (/ 1 3))))))","link":"9-2cbrtproblem334","ninf":0,"pinf":0,"end-est":2.8592450383022707,"name":"2cbrt (problem 3.3.4)","samplers":["default"],"time":86841.42700195312,"status":"imp-start","vars":["x"],"target":false,"end":2.63051098758136},{"bits":2432,"start":30.222464775570813,"input":"(/ (- 1 (cos x)) (sin x))","output":"(* 1 (/ (sin x) (+ (cos x) 1)))","link":"10-tanhfexample34","ninf":0,"pinf":0,"end-est":0.5284202760025005,"name":"tanhf (example 3.4)","samplers":["default"],"time":51172.2109375,"status":"eq-target","vars":["x"],"target":0.000625,"end":0.4384920000497587},{"bits":2432,"start":34.16168973131298,"input":"(/ (+ (- b) (sqrt (- (sqr b) (* 4 (* a c))))) (* 2 a))","output":"(if (<= b -1.751131060884064e+136) (/ (- b) a) (if (<= b -5.335815531470738e-240) (/ 1 (/ (* 2 a) (+ (- b) (sqrt (- (sqr b) (* 4 (* a c))))))) (if (<= b 5.845042913155354e+61) (/ 1 (* (- (- b) (sqrt (- (* b b) (* (* 4 a) c)))) (/ (/ 2 4) c))) (- (/ (+ b (- b)) (+ a a)) (/ c b)))))","link":"11-quadpp42positive","ninf":0,"pinf":0,"end-est":6.078206418658915,"name":"quadp (p42, positive)","samplers":["default","default","default"],"time":124200.916015625,"status":"gt-target","vars":["a","b","c"],"target":21.51568349447677,"end":5.376176978102462},{"bits":2944,"start":34.438091592742474,"input":"(/ (- (- b) (sqrt (- (sqr b) (* 4 (* a c))))) (* 2 a))","output":"(if (<= b -2.049536640230252e+150) (/ (* (/ c 2) 4) (- (/ (+ c c) (/ b a)) (- b (- b)))) (if (<= b 3.902728914492509e-158) (* (/ 4 2) (/ c (+ (- b) (sqrt (- (* b b) (* a (* c 4))))))) (if (<= b 5.845042913155354e+61) (/ 1 (/ (* 2 a) (- (- b) (sqrt (- (sqr b) (* 4 (* a c))))))) (- (/ c b) (/ b a)))))","link":"12-quadmp42negative","ninf":0,"pinf":0,"end-est":5.090534681871955,"name":"quadm (p42, negative)","samplers":["default","default","default"],"time":127442.75390625,"status":"gt-target","vars":["a","b","c"],"target":21.537857357826326,"end":5.5027912650509085},{"bits":1408,"start":61.40424674064236,"input":"(/ (log (- 1 x)) (log (+ 1 x)))","output":"(- (+ (* 1/2 (sqr x)) (+ 1 x)))","link":"13-qlogexample310","ninf":0,"pinf":0,"end-est":0.5716777813221573,"name":"qlog (example 3.10)","samplers":["default"],"time":22658.823974609375,"status":"eq-target","vars":["x"],"target":0.4463297341631591,"end":0.008125},{"bits":1408,"start":63.323661641605746,"input":"(- (- (* (+ n 1) (log (+ n 1))) (* n (log n))) 1)","output":"(- (* (log (+ n 1)) (+ n 1)) (+ (* (log n) (- n)) 1))","link":"14-logsexample38","ninf":0,"pinf":0,"end-est":60.73435355682203,"name":"logs (example 3.8)","samplers":["default"],"time":43125.350830078125,"status":"gt-target","vars":["n"],"target":60.78763823817359,"end":0.2685},{"bits":1408,"start":59.443513693513,"input":"(log (/ (- 1 eps) (+ 1 eps)))","output":"(- (+ (* 2/3 (pow eps 3)) (+ (* 2/5 (pow eps 5)) (* 2 eps))))","link":"15-logqproblem343","ninf":0,"pinf":0,"end-est":0.13714055965779817,"name":"logq (problem 3.4.3)","samplers":["default"],"time":91070.09790039062,"status":"eq-target","vars":["eps"],"target":0.06565423716474932,"end":0.08804107935288033},{"bits":2432,"start":59.91793067942972,"input":"(- (/ 1 x) (/ 1 (tan x)))","output":"(+ (* 2/945 (pow x 5)) (+ (* 1/3 x) (* 1/45 (pow x 3))))","link":"16-invcotexample39","ninf":0,"pinf":0,"end-est":0.34765625,"name":"invcot (example 3.9)","samplers":["default"],"time":26213.670166015625,"status":"eq-target","vars":["x"],"target":0.0759660601543468,"end":0.3295731203125902},{"bits":2432,"start":61.9783442604534,"input":"(/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1)))","output":"(if (<= (/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1))) -1.4449365230670285e-180) (+ (/ 1 b) (/ 1 a)) (if (<= (/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1))) 2.5007607876802412e-113) (+ (/ 1 b) (/ 1 a)) (+ (/ 1 b) (/ 1 a))))","link":"17-expq3problem342","ninf":0,"pinf":0,"end-est":4.24688513434934,"name":"expq3 (problem 3.4.2)","samplers":["default","default","default"],"time":233948.95581054688,"status":"gt-target","vars":["a","b","eps"],"target":14.651067317747806,"end":0.014198120312590145},{"bits":1408,"start":45.739517733726835,"input":"(/ (exp x) (- (exp x) 1))","output":"(if (<= x -9.950485992669078e-09) (/ 1 (- 1 (exp (- x)))) (if (<= x 0.16631490308113306) (+ (/ 1 x) (+ 1/2 (* 1/12 x))) (/ 1 (- 1 (exp (- x))))))","link":"18-expq2section311","ninf":0,"pinf":0,"end-est":0.22577593043685318,"name":"expq2 (section 3.11)","samplers":["default"],"time":20018.59912109375,"status":"gt-target","vars":["x"],"target":30.12948710533904,"end":0.05791712397806687},{"bits":1408,"start":59.33343129621902,"input":"(- (exp x) 1)","output":"(+ (* (* x x) (+ 1/2 (* x 1/6))) x)","link":"19-expm1example37","ninf":0,"pinf":0,"end-est":0.3642608971118385,"name":"expm1 (example 3.7)","samplers":["default"],"time":35543.492919921875,"status":"eq-target","vars":["x"],"target":0.06436560156295071,"end":0.06598364687698317},{"bits":1408,"start":33.44360243099122,"input":"(- (exp (* a x)) 1)","output":"(if (<= (* a x) -1.6665755921255327e-09) (- (exp (* a x)) 1) (+ (* x a) (* 1/2 (* (* x a) (* x a)))))","link":"20-expaxsection35","ninf":0,"pinf":0,"end-est":0.30826629080627144,"name":"expax (section 3.5)","samplers":["default","default"],"time":31542.9580078125,"status":"gt-target","vars":["a","x"],"target":7.952127997421758,"end":0.18645915544792366},{"bits":1408,"start":33.99583817370671,"input":"(+ (- (exp x) 2) (exp (- x)))","output":"(+ (* 1/12 (pow x 4)) (+ (* 1/360 (pow x 6)) (sqr x)))","link":"21-exp2problem337","ninf":0,"pinf":0,"end-est":0.7811424888959018,"name":"exp2 (problem 3.3.7)","samplers":["default"],"time":51116.8779296875,"status":"gt-target","vars":["x"],"target":8.659910873648657,"end":0.11144644300676601},{"bits":1152,"start":9.49656829978195,"input":"(+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))","output":"(/ (/ (- (/ 2 x) 0) (- x 1)) (+ 1 x))","link":"22-3fracproblem333","ninf":0,"pinf":0,"end-est":0.060878759768442016,"name":"3frac (problem 3.3.3)","samplers":["default"],"time":139599.76000976562,"status":"eq-target","vars":["x"],"target":0.23795078190808433,"end":0.06871936093777044},{"bits":2432,"start":36.40936010427848,"input":"(- (tan (+ x eps)) (tan x))","output":"(if (<= eps -2.4610266585566768e-113) (/ (- (sqr (/ (+ (tan x) (tan eps)) (- 1 (* (tan x) (tan eps))))) (sqr (tan x))) (+ (/ (+ (tan x) (tan eps)) (- 1 (* (tan x) (tan eps)))) (tan x))) (if (<= eps 3.1547769921923584e-35) (+ (* (sqr x) (cube eps)) (+ eps (* (cube x) (pow eps 4)))) (- (* (/ (+ (tan eps) (tan x)) (- 1 (/ (cube (* (tan eps) (sin x))) (cube (cos x))))) (+ (sqr 1) (+ (sqr (* (tan x) (tan eps))) (* 1 (* (tan x) (tan eps)))))) (tan x))))","link":"23-2tanproblem332","ninf":0,"pinf":0,"end-est":16.79675543908866,"name":"2tan (problem 3.3.2)","samplers":["default","default"],"time":153829.44311523438,"status":"gt-target","vars":["x","eps"],"target":24.868488975311955,"end":11.1570419418963},{"bits":1408,"start":29.901471078747512,"input":"(- (sqrt (+ x 1)) (sqrt x))","output":"(/ 1 (+ (sqrt (+ x 1)) (sqrt x)))","link":"24-2sqrtexample31","ninf":0,"pinf":0,"end-est":0.19988251953688405,"name":"2sqrt (example 3.1)","samplers":["default"],"time":20755.375,"status":"eq-target","vars":["x"],"target":0.16316052656439306,"end":0.16316052656439306},{"bits":2432,"start":36.71325510564527,"input":"(- (sin (+ x eps)) (sin x))","output":"(if (<= eps -3.645937152382937e+19) (- (+ (* (sin x) (cos eps)) (* (cos x) (sin eps))) (sin x)) (if (<= eps 6.326235572596747e-15) (* 2 (* (sin (/ eps 2)) (cos (/ (+ (+ x eps) x) 2)))) (- (+ (* (sin x) (cos eps)) (* (cos x) (sin eps))) (sin x))))","link":"25-2sinexample33","ninf":0,"pinf":0,"end-est":0.40463013074677723,"name":"2sin (example 3.3)","samplers":["default","default"],"time":82629.40185546875,"status":"gt-target","vars":["x","eps"],"target":14.900038199925003,"end":1.0798819096901375},{"bits":1152,"start":19.330732255826693,"input":"(- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))","output":"(* (/ 1 (+ (sqrt (+ 1 x)) (sqrt x))) (/ 1 (* (sqrt x) (sqrt (+ x 1)))))","link":"26-2isqrtexample36","ninf":0,"pinf":0,"end-est":0.3721339476841681,"name":"2isqrt (example 3.6)","samplers":["default"],"time":35296.56103515625,"status":"eq-target","vars":["x"],"target":0.714170361427429,"end":0.3941741281572718},{"bits":1408,"start":14.541859386925417,"input":"(- (atan (+ N 1)) (atan N))","output":"(atan2 (+ 1 0) (+ (* (+ N 1) N) 1))","link":"27-2atanexample35","ninf":0,"pinf":0,"end-est":0.29506882110978144,"name":"2atan (example 3.5)","samplers":["default"],"time":15547.489990234375,"status":"eq-target","vars":["N"],"target":0.39299853686879893,"end":0.39174853686879885}]} \ No newline at end of file diff --git a/www/doc/1.2/results-old.json b/www/doc/1.2/results-old.json new file mode 100755 index 000000000..49893537b --- /dev/null +++ b/www/doc/1.2/results-old.json @@ -0,0 +1 @@ +{"bit_width":64,"date":1490840216,"commit":"58bf255242aa4ff0c47bb93e3c2ea80079b951c5","branch":"master","flags":["precision:double","setup:simplify","reduce:regimes","reduce:taylor","reduce:simplify","reduce:avg-error","rules:arithmetic","rules:polynomials","rules:fractions","rules:exponents","rules:trigonometry","generate:rr","generate:taylor","generate:simplify"],"points":256,"iterations":3,"tests":[{"status":"imp-start","target":false,"start":30.72710658756978,"vars":["x","n"],"samplers":["default","default"],"input":"(- (pow (+ x 1) (/ 1 n)) (pow x (/ 1 n)))","output":"(if (<= n -2.3031253660826823e+26) (- (- (/ (/ 1 x) n) (/ (log x) (* n (* n x)))) (/ (/ 1/2 n) (sqr x))) (if (<= n 479155699082691.3) (cbrt (cube (cbrt (cube (cbrt (cube (- (pow (+ x 1) (/ 1 n)) (pow x (/ 1 n))))))))) (- (- (/ (/ 1 x) n) (/ (log x) (* n (* n x)))) (/ (/ 1/2 n) (sqr x)))))","bits":128,"pinf":0,"ninf":0,"end-est":26.115579094525142,"name":"NMSE problem 3.4.6","end":8.352001623029237,"time":96880.43798828125,"link":"0-NMSEproblem346"},{"status":"imp-start","target":false,"start":31.62500906332104,"vars":["x"],"samplers":["default"],"input":"(/ (- x (sin x)) (- x (tan x)))","output":"(if (<= x -3.150653682326084e-06) (- (/ x (- x (tan x))) (/ (sin x) (- x (tan x)))) (if (<= x 0.1680384430276768) (- (* 9/40 (sqr x)) (+ (* 27/2800 (pow x 4)) 1/2)) (- (/ x (- x (tan x))) (/ (sin x) (- x (tan x))))))","bits":128,"pinf":0,"ninf":0,"end-est":0.16733880207871452,"name":"NMSE problem 3.4.5","end":0.1104513958526418,"time":47490.5791015625,"link":"1-NMSEproblem345"},{"status":"imp-start","target":false,"start":45.09950437667379,"vars":["x"],"samplers":["default"],"input":"(sqrt (/ (- (exp (* 2 x)) 1) (- (exp x) 1)))","output":"(if (<= x -8.867720861083589e-13) (sqrt (/ (- (exp (* 2 x)) 1) (- (exp x) 1))) (sqrt (+ (* 1/2 (sqr x)) (+ 2 x))))","bits":128,"pinf":0,"ninf":0,"end-est":0.7376765013424993,"name":"NMSE problem 3.4.4","end":7.500544686712205,"time":26112.76806640625,"link":"2-NMSEproblem344"},{"status":"imp-start","target":false,"start":31.356953350333576,"vars":["x"],"samplers":["default"],"input":"(/ (- 1 (cos x)) (sqr x))","output":"(* (/ (sin x) x) (/ (/ (sin x) (+ 1 (cos x))) x))","bits":128,"pinf":0,"ninf":0,"end-est":0.192046331613414,"name":"NMSE problem 3.4.1","end":0.30161782915000535,"time":43265.260986328125,"link":"3-NMSEproblem341"},{"status":"imp-start","target":false,"start":40.184769151759774,"vars":["N"],"samplers":["default"],"input":"(- (log (+ N 1)) (log N))","output":"(if (<= N 519383.0646430557) (log (/ (+ N 1) N)) (+ (/ (- (/ 1/3 N) 1/2) (sqr N)) (/ 1 N)))","bits":128,"pinf":0,"ninf":0,"end-est":0.09151380401675037,"name":"NMSE problem 3.3.6","end":19.38968452894342,"time":18756.22607421875,"link":"4-NMSEproblem336"},{"status":"imp-start","target":false,"start":37.07285680541725,"vars":["x","eps"],"samplers":["default","default"],"input":"(- (cos (+ x eps)) (cos x))","output":"(if (<= eps -1.3296779497386022e-08) (/ (- (cube (* (cos eps) (cos x))) (cube (+ (* (sin eps) (sin x)) (cos x)))) (+ (sqr (* (cos eps) (cos x))) (* (+ (* (cos eps) (cos x)) (+ (* (sin x) (sin eps)) (cos x))) (+ (* (sin x) (sin eps)) (cos x))))) (if (<= eps 7.134416671297405e-12) (- (* (* eps 1/6) (cube x)) (* eps (+ (* 1/2 eps) x))) (/ (- (cube (* (cos eps) (cos x))) (cube (+ (* (sin eps) (sin x)) (cos x)))) (+ (sqr (* (cos eps) (cos x))) (* (+ (* (cos eps) (cos x)) (+ (* (sin x) (sin eps)) (cos x))) (+ (* (sin x) (sin eps)) (cos x)))))))","bits":128,"pinf":0,"ninf":0,"end-est":15.049825158123982,"name":"NMSE problem 3.3.5","end":3.8473381505495694,"time":68603.96508789062,"link":"5-NMSEproblem335"},{"status":"apx-start","target":false,"start":29.326846493043334,"vars":["x"],"samplers":["default"],"input":"(- (pow (+ x 1) (/ 1 3)) (pow x (/ 1 3)))","output":"(exp (cube (cbrt (log (- (pow (+ x 1) (/ 1 3)) (pow x (/ 1 3)))))))","bits":128,"pinf":0,"ninf":0,"end-est":26.112737951959613,"name":"NMSE problem 3.3.4","end":29.330084230611476,"time":48849.850830078125,"link":"6-NMSEproblem334"},{"status":"imp-start","target":false,"start":14.327571332282405,"vars":["x"],"samplers":["default"],"input":"(- (/ 1 (+ x 1)) (/ 1 x))","output":"(if (<= x -6572693219723.217) (- (/ (/ 1 x) (sqr x)) (/ (/ 1 x) x)) (if (<= x 14125114759858.025) (/ (- x (+ 1 x)) (* (+ x 1) x)) (- (/ (/ 1 x) (sqr x)) (/ (/ 1 x) x))))","bits":128,"pinf":0,"ninf":0,"end-est":0.078125,"name":"NMSE problem 3.3.1","end":0.07644812031259013,"time":19662.1669921875,"link":"7-NMSEproblem331"},{"status":"imp-start","target":false,"start":37.568586126435775,"vars":["a","b/2","c"],"samplers":["default","default","default"],"input":"(/ (+ (- b/2) (sqrt (- (sqr b/2) (* a c)))) a)","output":"(if (<= b/2 -5.149274995892533e+86) (- (* (/ 1/2 b/2) c) (* 2 (/ b/2 a))) (if (<= b/2 9.946391916507581e-83) (/ 1 (/ a (+ (- b/2) (sqrt (- (sqr b/2) (* a c)))))) (- (/ (+ b/2 (- b/2)) a) (* 1/2 (/ c b/2)))))","bits":128,"pinf":0,"ninf":0,"end-est":6.760667970562651,"name":"NMSE problem 3.2.1, positive","end":4.75003112024967,"time":50862.839111328125,"link":"8-NMSEproblem321positive"},{"status":"imp-start","target":false,"start":35.67879826787097,"vars":["a","b/2","c"],"samplers":["default","default","default"],"input":"(/ (- (- b/2) (sqrt (- (sqr b/2) (* a c)))) a)","output":"(if (<= b/2 -0.004102676064970259) (* (/ c b/2) -1/2) (if (<= b/2 -7.674146973599803e-149) (/ (/ (* a c) (+ (- b/2) (sqrt (- (sqr b/2) (* a c))))) a) (if (<= b/2 9.060571198789832e+80) (/ (- (- b/2) (sqrt (- (sqr b/2) (* a c)))) a) (* -2 (/ b/2 a)))))","bits":128,"pinf":0,"ninf":0,"end-est":6.283356310763793,"name":"NMSE problem 3.2.1, negative","end":5.53668681524491,"time":41098.823974609375,"link":"9-NMSEproblem321negative"},{"status":"gt-target","target":12.539481177734315,"start":35.88674549677141,"vars":["a","x"],"samplers":["default","default"],"input":"(- (exp (* a x)) 1)","output":"(if (<= (* a x) -2.6715888843965976e-09) (- (exp (* a x)) 1) (* x a))","bits":128,"pinf":0,"ninf":0,"end-est":0.43938819654193784,"name":"NMSE section 3.5","end":0.09584099696101328,"time":19852.485107421875,"link":"10-NMSEsection35"},{"status":"gt-target","target":45.33809150635401,"start":45.32749786530321,"vars":["x"],"samplers":["default"],"input":"(/ (exp x) (- (exp x) 1))","output":"(if (<= x -3.150653682326084e-06) (/ 1 (- 1 (exp (- x)))) (+ (* 1/12 x) (+ 1/2 (/ 1 x))))","bits":128,"pinf":0,"ninf":0,"end-est":0.6084361289342824,"name":"NMSE section 3.11","end":0.12805703358889042,"time":23151.529052734375,"link":"11-NMSEsection311"},{"status":"eq-target","target":0.06576167289182676,"start":59.41688754122086,"vars":["eps"],"samplers":["default"],"input":"(log (/ (- 1 eps) (+ 1 eps)))","output":"(- (+ (+ (* (cube eps) 2/3) (* 2/5 (pow eps 5))) (* 2 eps)))","bits":128,"pinf":0,"ninf":0,"end-est":0.15924746790050592,"name":"NMSE problem 3.4.3","end":0.08275227445477747,"time":31097.13818359375,"link":"12-NMSEproblem343"},{"status":"gt-target","target":14.14172034209319,"start":61.97294495933572,"vars":["a","b","eps"],"samplers":["default","default","default"],"input":"(/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1)))","output":"(if (<= (/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1))) -9.296169205569568e-188) (+ (/ 1 a) (/ 1 b)) (if (<= (/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1))) 7.819285243784398e-190) (+ (/ 1 a) (/ 1 b)) (+ (/ 1 a) (/ 1 b))))","bits":128,"pinf":0,"ninf":0,"end-est":3.590759504240281,"name":"NMSE problem 3.4.2","end":0.012948120312590145,"time":200928.28002929688,"link":"13-NMSEproblem342"},{"status":"gt-target","target":8.680496323252925,"start":34.42699152807259,"vars":["x"],"samplers":["default"],"input":"(+ (- (exp x) 2) (exp (- x)))","output":"(+ (sqr x) (+ (* 1/12 (pow x 4)) (* 1/360 (pow x 6))))","bits":128,"pinf":0,"ninf":0,"end-est":0.28275930527467336,"name":"NMSE problem 3.3.7","end":0.1067912674976738,"time":18596.828125,"link":"14-NMSEproblem337"},{"status":"eq-target","target":0.24535050244616535,"start":9.51064615742219,"vars":["x"],"samplers":["default"],"input":"(+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))","output":"(/ 2 (* (+ x (sqr x)) (- x 1)))","bits":128,"pinf":0,"ninf":0,"end-est":0.0625,"name":"NMSE problem 3.3.3","end":0.25036234463429635,"time":57014.72998046875,"link":"15-NMSEproblem333"},{"status":"gt-target","target":26.24923754199063,"start":36.519809893592864,"vars":["x","eps"],"samplers":["default","default"],"input":"(- (tan (+ x eps)) (tan x))","output":"(if (<= eps -6.169079171368681e-49) (/ (/ (- (sqr (cos x)) (sqr (cube (cbrt (* (cotan (+ x eps)) (sin x)))))) (+ (* (cotan (+ eps x)) (sin x)) (cos x))) (* (cotan (+ x eps)) (cos x))) (if (<= eps 2.0116360245016707e-26) (+ (* (sqr x) (cube eps)) (+ eps (* (cube x) (pow eps 4)))) (/ (/ (- (sqr (cos x)) (sqr (cube (cbrt (* (cotan (+ x eps)) (sin x)))))) (+ (* (cotan (+ eps x)) (sin x)) (cos x))) (* (cotan (+ x eps)) (cos x)))))","bits":128,"pinf":0,"ninf":0,"end-est":27.67891718929942,"name":"NMSE problem 3.3.2","end":24.756494557676795,"time":63485.365966796875,"link":"16-NMSEproblem332"},{"status":"gt-target","target":25.77374001912148,"start":37.72618589177047,"vars":["a","b","c"],"samplers":["default","default","default"],"input":"(/ (+ (- b) (sqrt (- (sqr b) (* 4 (* a c))))) (* 2 a))","output":"(if (<= b -5.149274995892533e+86) (- (/ c b) (/ b a)) (if (<= b 9.946391916507581e-83) (/ 1 (/ (* 2 a) (+ (- b) (sqrt (- (sqr b) (* 4 (* a c))))))) (/ (* (/ 4 2) c) (- (/ (* c 2) (/ b a)) (* b 2)))))","bits":128,"pinf":0,"ninf":0,"end-est":6.740241807359317,"name":"NMSE p42, positive","end":6.310074296845912,"time":68592.75610351562,"link":"17-NMSEp42positive"},{"status":"gt-target","target":23.40536923119662,"start":35.66770760005187,"vars":["a","b","c"],"samplers":["default","default","default"],"input":"(/ (- (- b) (sqrt (- (sqr b) (* 4 (* a c))))) (* 2 a))","output":"(if (<= b -0.004102676064970259) (* (/ -2 2) (/ c b)) (if (<= b -7.674146973599803e-149) (/ (/ (* 4 (* a c)) (+ (- b) (sqrt (- (sqr b) (* 4 (* a c)))))) (* 2 a)) (if (<= b 9.060571198789832e+80) (- (/ (- b) (* 2 a)) (/ (sqrt (- (sqr b) (* 4 (* a c)))) (* 2 a))) (- (/ c b) (/ b a)))))","bits":128,"pinf":0,"ninf":0,"end-est":6.283669107106033,"name":"NMSE p42, negative","end":5.544343202430409,"time":74686.93579101562,"link":"18-NMSEp42negative"},{"status":"eq-target","target":0.07492121385885234,"start":59.92910465279442,"vars":["x"],"samplers":["default"],"input":"(- (/ 1 x) (cotan x))","output":"(+ (* 1/45 (cube x)) (+ (* (pow x 5) 2/945) (* x 1/3)))","bits":128,"pinf":0,"ninf":0,"end-est":0.31640625,"name":"NMSE example 3.9","end":0.341125,"time":19957.39599609375,"link":"19-NMSEexample39"},{"status":"lt-target","target":0,"start":62.983165481898844,"vars":["N"],"samplers":["default"],"input":"(- (- (* (+ N 1) (log (+ N 1))) (* N (log N))) 1)","output":"(- (exp (- (log (- (sqr (cube (* (cbrt (+ N 1)) (cbrt (log (+ N 1)))))) (sqr (* N (log N))))) (log (+ (* (log N) N) (* (log (+ N 1)) (+ N 1)))))) 1)","bits":128,"pinf":0,"ninf":0,"end-est":61.26976387791292,"name":"NMSE example 3.8","end":61.339891393664374,"time":120993.76293945312,"link":"20-NMSEexample38"},{"status":"eq-target","target":0.06211560156295071,"start":59.381589721029606,"vars":["x"],"samplers":["default"],"input":"(- (exp x) 1)","output":"(+ x (* (sqr x) (+ (* 1/6 x) 1/2)))","bits":128,"pinf":0,"ninf":0,"end-est":0.41917203895823363,"name":"NMSE example 3.7","end":0.06528552656439303,"time":9809.6240234375,"link":"21-NMSEexample37"},{"status":"eq-target","target":0.6404688144198558,"start":19.20459512853389,"vars":["x"],"samplers":["default"],"input":"(- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))","output":"(* (/ 1 (+ (sqrt (+ 1 x)) (sqrt x))) (/ 1 (* (sqrt x) (sqrt (+ x 1)))))","bits":128,"pinf":0,"ninf":0,"end-est":0.38355263675818835,"name":"NMSE example 3.6","end":0.3995771199558376,"time":22303.14697265625,"link":"22-NMSEexample36"},{"status":"eq-target","target":0.3535924396191792,"start":14.816015150117126,"vars":["N"],"samplers":["default"],"input":"(- (atan (+ N 1)) (atan N))","output":"(atan2 (+ 1 0) (+ (+ (sqr N) N) 1))","bits":128,"pinf":0,"ninf":0,"end-est":0.28737647630464624,"name":"NMSE example 3.5","end":0.3545099208695398,"time":13250.199951171875,"link":"23-NMSEexample35"},{"status":"eq-target","target":0.00025,"start":30.564429941581903,"vars":["x"],"samplers":["default"],"input":"(/ (- 1 (cos x)) (sin x))","output":"(* 1 (/ (sin x) (+ (cos x) 1)))","bits":128,"pinf":0,"ninf":0,"end-est":0.3672159192193488,"name":"NMSE example 3.4","end":0.4536472361174264,"time":29972.529052734375,"link":"24-NMSEexample34"},{"status":"gt-target","target":26.637778727998082,"start":36.25290408910504,"vars":["x","eps"],"samplers":["default","default"],"input":"(- (sin (+ x eps)) (sin x))","output":"(if (<= eps -6.169079171368681e-49) (/ (- (sqr (+ (* (sin x) (cos eps)) (* (cos x) (sin eps)))) (sqr (sin x))) (+ (+ (* (sin x) (cos eps)) (* (cos x) (sin eps))) (sin x))) (if (<= eps 2.0116360245016707e-26) (- eps (* (* (+ x eps) (* x eps)) 1/2)) (+ (* (sin x) (cos eps)) (/ (- (sqr (* (cos x) (sin eps))) (sqr (sin x))) (+ (* (cos x) (sin eps)) (sin x))))))","bits":128,"pinf":0,"ninf":0,"end-est":14.515236082978266,"name":"NMSE example 3.3","end":2.0782981277949357,"time":62088.2919921875,"link":"25-NMSEexample33"},{"status":"eq-target","target":0.4427242395058597,"start":61.373439946309574,"vars":["x"],"samplers":["default"],"input":"(/ (log (- 1 x)) (log (+ 1 x)))","output":"(- (+ (+ (* 1/2 (sqr x)) x) 1))","bits":128,"pinf":0,"ninf":0,"end-est":0.35614265339161655,"name":"NMSE example 3.10","end":0.000875,"time":15522.3759765625,"link":"26-NMSEexample310"},{"status":"eq-target","target":0.164660526564393,"start":29.410104469288694,"vars":["x"],"samplers":["default"],"input":"(- (sqrt (+ x 1)) (sqrt x))","output":"(/ 1 (+ (sqrt (+ x 1)) (sqrt x)))","bits":128,"pinf":0,"ninf":0,"end-est":0.15234375,"name":"NMSE example 3.1","end":0.164660526564393,"time":8996.474853515625,"link":"27-NMSEexample31"}],"note":false,"seed":"#(2606739721 3337331833 2041942718 3037006954 1385554395 1942462848)"} \ No newline at end of file diff --git a/www/doc/1.2/team.png b/www/doc/1.2/team.png new file mode 100644 index 000000000..935650305 Binary files /dev/null and b/www/doc/1.2/team.png differ diff --git a/www/doc/1.2/tutorial-2.html b/www/doc/1.2/tutorial-2.html new file mode 100644 index 000000000..64d2a8dc9 --- /dev/null +++ b/www/doc/1.2/tutorial-2.html @@ -0,0 +1,282 @@ + + + + + Herbie Tutorial, Part 2 + + + +
        + +

        Herbie Tutorial, Part 2

        +

        See the main page for more info on Herbie.

        +
        + +

        + Part 1 of this tutorial described how + Herbie can be used to automatically rewrite + floating point expressions, to make them more accurate. + Part 1 focused on running Herbie and reading its results; + this Part 2 will instead work through applying Herbie to a realistic program. +

        + +

        Finding numerical expressions

        + +

        + As an example realistic program, we'll use math.js, + an extensive math library for JavaScript. + In particular, we'll walk through bug 208, + which found inaccuracy in the implementation of complex square root; + for a full write-up of the bug itself, + check out this blog post + by one of the Herbie authors. +

        + +

        + To use Herbie, you first need to find some floating-point expressions to feed to Herbie. + In the case of math.js, the floating-point expressions of interest + are the various functions that compute mathematical functions; + in your code, there's a good chance + that a small core of your application does the mathematical computations, + and the rest sets up parameters, handles control flow, visualizes or print results, and so on. + The mathematical core is what Herbie will be interested in. +

        + +

        + For example, in the case of math.js, the mathematical core + is in lib/function/. + Each file in each subdirectory contains a collection of mathematical functions, + each of which is potentially inaccurate. + You can start by sending all of them into Herbie, or only the most important ones. + Here, let's look at just the file + arithmetic/sqrt.js, + which contains real and complex square roots. + In full, the code of interest is: +

        + +
        math.sqrt = function sqrt (x) {
        +  if (arguments.length != 1) {
        +    throw new math.error.ArgumentsError('sqrt', arguments.length, 1);
        +  }
        +
        +  if (isNumber(x)) {
        +    if (x >= 0) {
        +      return Math.sqrt(x);
        +    }
        +    else {
        +      return sqrt(new Complex(x, 0));
        +    }
        +  }
        +
        +  if (isComplex(x)) {
        +    var r = Math.sqrt(x.re * x.re + x.im * x.im);
        +    if (x.im >= 0) {
        +      return new Complex(
        +          0.5 * Math.sqrt(2.0 * (r + x.re)),
        +          0.5 * Math.sqrt(2.0 * (r - x.re))
        +      );
        +    }
        +    else {
        +      return new Complex(
        +          0.5 * Math.sqrt(2.0 * (r + x.re)),
        +          -0.5 * Math.sqrt(2.0 * (r - x.re))
        +      );
        +    }
        +  }
        +
        +  if (x instanceof BigNumber) {
        +    if (x.isNegative()) {
        +      // negative value -> downgrade to number to do complex value computation
        +      return sqrt(x.toNumber());
        +    }
        +    else {
        +      return x.sqrt();
        +    }
        +  }
        +
        +  if (isCollection(x)) {
        +    return collection.deepMap(x, sqrt);
        +  }
        +
        +  if (isBoolean(x) || x === null) {
        +    return sqrt(+x);
        +  }
        +
        +  throw new math.error.UnsupportedTypeError('sqrt', math['typeof'](x));
        +};
        + +

        Extracting expressions

        + +

        + The code above is complex, + with argument checks, dispatching over five possible types, and error handling. + Herbie does not handle complex data structures (only floating-point values), + so we'll want to break up the code above into multiple inputs, + one for each type of data structure. + Let's look at the isComplex(x) case: +

        + +
        var r = Math.sqrt(x.re * x.re + x.im * x.im);
        +if (x.im >= 0) {
        +  return new Complex(
        +      0.5 * Math.sqrt(2.0 * (r + x.re)),
        +      0.5 * Math.sqrt(2.0 * (r - x.re))
        +  );
        +}
        +else {
        +  return new Complex(
        +      0.5 * Math.sqrt(2.0 * (r + x.re)),
        +      -0.5 * Math.sqrt(2.0 * (r - x.re))
        +  );
        +}
        + +

        + This code contains a branch: one option for non-negative x.im, + and one for positive x.im. + While Herbie supports an if construct, + it's usually better to encode branches as separate inputs to Herbie. +

        + +

        + Finally, each branch access fields of a data structure + (x is of type Complex) + and constructs new data structures. + Since Herbie does not understand complex data structures, + we must write each floating-point value used in constructing the final output + as its own test case. +

        + +

        + So, this isComplex(x) case would become four inputs to Herbie: + a real and an imaginary part, for each negative or non-negative x.im. + Note that the r variable is computed outside the branch; + it will have to be duplicated in each input. + Each input is a single floating-point expression + that computes a single floating-point output without branches, loops, or data structures: +

        + +
        var r = Math.sqrt(xre * xre + xim * xim);
        +0.5 * Math.sqrt(2.0 * (r + xre)),
        +// xim ≥ 0
        + +
        var r = Math.sqrt(xre * xre + xim * xim);
        +0.5 * Math.sqrt(2.0 * (r - xre)),
        +// xim ≥ 0
        + +
        var r = Math.sqrt(xre * xre + xim * xim);
        +0.5 * Math.sqrt(2.0 * (r + xre)),
        +// xim < 0
        + +
        var r = Math.sqrt(xre * xre + xim * xim);
        +-0.5 * Math.sqrt(2.0 * (r - xre)),
        +// xim < 0
        + +

        + Note that x.im and x.re + have changed to xim and xre. + This is to emphasize that there is no longer an x structure, + just its floating-point fields. + The comment below each case reminds us what the bounds on the input variables are. +

        + +

        Translating to Herbie's input language

        + +

        + Now that we have simple floating-point expressions, + we can translate them to Herbie's input language. + The input language is a variant of Scheme. + For each input, we will write a herbie-test declaration; + each declaration will have a list of input variables, + a description of the input, + and the floating-point expression itself. + Here's how we'd start for the first input above: +

        + +
        (herbie-test (xim xre)
        +    "arithmetic/sqrt.js, isComplex(x), x>=0"
        +    ?)
        + +

        + The question mark is what we will fill in with the expression. + But before we do that, take a look at the other fields. + The variables are specified as (xim xre), + which tells Herbie that there are two input variables named xim and xre; + the parentheses are mandatory. + r isn't on that list, because even though it is a variable in the code above, + it's not an input variable: + it's not an argument to our code, but just a value computed internally. +

        + +

        + Now, we must translate the expression itself. + We can define r with a let* expression: +

        + +
        (herbie-test (xim xre)
        +    "arithmetic/sqrt.js, isComplex(x), x>=0"
        +    (let* ([r (sqrt (+ (sqr xre) (sqr xim)))])
        +      ?))
        + +

        + Note the peculiar syntax of let*; + the first argument is a list of square-bracketed binders, + each of which has a variable name (like r) + and an expression to bind that variable to (here, (sqrt (+ (sqr xre) (sqr xim)))). + There's only one binder here, but you could have more if you wanted. +

        + +

        + Inside the body of the let*, which is its second argument (the question mark), + you can write another expression which can refer to any of the bound variable names. + We'll translate the second line there: +

        + +
        (herbie-test (xim xre)
        +    "arithmetic/sqrt.js, isComplex(x), x>=0"
        +    (let* ([r (sqrt (+ (sqr xre) (sqr xim)))])
        +      (* 0.5 (sqrt (* 2.0 (+ r xre))))))
        + +

        + Translating expressions is not too hard—Herbie understands many common mathematical functions, + and even has shortcuts, such as sqr for squaring numbers. +

        + +

        + The final step is to add our input bound, xim ≥ 0. + You do this by changing the variable declaration in the first argument to herbie-test. + Instead of just writing xim, + write a binder, which has the variable name xim + and a distribution to sample xim from: +

        + +
        (herbie-test ([xim (>= default 0)] xre)
        +    "arithmetic/sqrt.js, isComplex(x), x>=0"
        +    (let* ([r (sqrt (+ (sqr xre) (sqr xim)))])
        +      (* 0.5 (sqrt (* 2.0 (+ r xre))))))
        + +

        + We use the distribution (>= default 0), + which means to sample values from default + and only keep them if they are greater than 0. + You usually want to use default as the input distribution, + since it specifically tries very large and very small inputs + in an effort to find inaccurate inputs; + but there are other distributions as well, + including integer for 32-bit integers + and (uniform a b) for uniformly-distributed real values. +

        + +

        + This finishes our first input to Herbie. + We can translate the other four cases at this point, or go ahead with the first case. + For the sake of the tutorial, let's move ahead with one input for now. +

        + +

        Running Herbie

        + +

        + Running Herbie is exactly like before: +

        + + + diff --git a/www/doc/1.2/tutorial.html b/www/doc/1.2/tutorial.html new file mode 100644 index 000000000..2e4f92f61 --- /dev/null +++ b/www/doc/1.2/tutorial.html @@ -0,0 +1,190 @@ + + + + + Herbie Tutorial + + + +
        + +

        Herbie Tutorial, Part 1

        +

        See the main page for more info on Herbie.

        +
        + +

        + Herbie is a tool + that automatically rewrites floating point expressions to make them more accurate. + It's well-known that floating point arithmetic is inaccurate; + hence the jokes that 0.1 + 0.2 ≠ 0.3 for a computer. + But to understand the inaccuracies and reduce them is a much harder task. + Usually, programs that use floating point arithmetic + are just written with the hope that these inaccuracies will not cause bugs, + and when they do, these bugs are mysterious and hard to fix. +

        + +

        + To get started using Herbie, download and install it, + including running the test suite (as described in the installation instructions) + to ensure that you have Herbie working properly. + Now that Herbie is installed, you're ready to begin using it. +

        + +

        The format of input files

        + +

        + Herbie is a stand-alone tool, which accepts floating point expressions as inputs + and produces floating point expressions as output. + These floating point expressions are written in Herbie's custom input language, + which is approximately a subset of Racket. + For an example, open up bench/tutorial.rkt. + You'll see three blocks of code, the first of which is +

        + +
        (herbie-test (x)
        +  "Cancel like terms"
        +  (- (+ 1 x) x))
        + +

        + Each of these blocks describes a single input to Herbie; + the block has a list of variables: (x); + a name: "Cancel like terms"; + and the expression itself: (- (+ 1 x) x). + Inputs can also have an optional "target"—an equivalent expression + for Herbie to compare its results to. + That's used for tests, but isn't that useful to you. +

        + +

        + Take a look at the three test cases in the file. If you're not + familiar with Lisp syntax, it might take a bit to get used to the + way expressions are written. As you can see, Herbie has common + mathematical operators built in, from arithmetic to more + complicated functions like as pow + and sin. +

        + +

        The Herbie main report page

        + +

        Now run the tutorial file through Herbie by running

        + +
        racket src/herbie.rkt report bench/tutorial.rkt graphs/
        + +

        + from the base Herbie directory. + A graphs directory should appear + (if it already existed, its contents will be replaced), + which contains a detailed description of Herbie's results and how it got them. + Open up graphs/report.html with your web browser to view these results. +

        + +
        + +
        A screenshot of the main Herbie reports page for the tutorial file.
        +
        + +

        + There's a lot going on in this page, so let's break it down. + On top, you see a quick summary of the results: + the running time, the number of expressions improved, + the total number of expressions run on, and the starting/ending bits of accuracy. + You should see that Herbie considers itself to have improved 2/2 expressions, + even though it ran on three. + The reason is that Herbie doesn't consider itself to have “improved” an expression + until it improves it accuracy by at least one bit. + Since one of the expressions was pretty accurate to start (less than a bit of error), + it isn't even a contender, and the improved version doesn't count as an improvement. +

        + +

        + Next, there's a graph summarizing the test results. + Each horizontal row is a single expression, + and the line stretches from the original accuracy of that expression + to the accuracy of Herbie's output. + You should see one long arrow (for the expression (x + 1)2 - 1), + one short arrow (for the expression (1 + x) - x) + and one bare arrowhead (for the expression ((x + y) + z) - (x + (y + z))). + This corresponds to the fact that the first expression is pretty inaccurate (for small x), + the second is also inaccurate (for large x), + and the last one is pretty accurate (though not exactly so). +

        + +

        + Next to the graph is a grid of blocks, each one listing the bits improved for a test case. + These are in the same order as the arrows; + mouse over one to see the associated arrow light up. + The colors describe how Herbie scores itself: + white for already accurate, green for improved, orange for not improved, and red for made worse. + You won't be seeing the last one (if you do, report it as a bug). +

        + +

        + Below that, you have a lot of information about Herbie's internal configuration. + You can usually skip it, but many of these parameters are configurable from the command line. + The most useful one is the seed. + Herbie uses random sampling internally, so different runs can yield slightly different results. + and can be set with the --seed flag to Herbie. +

        + +

        + Finally, the page has a table of each expression, + with the starting error, final error, runtime, and a link to details. +

        + +

        The Herbie details page

        + +

        + Click on the last row, for the expression (x + 1)2 - 1. + This page explains what Herbie did to that expression, + including how the error is distributed for that expression, + the final expression Herbie came up with, + and how it derived that expression. +

        + +
        + +
        A screenshot of a details page in the Herbie report for the tutorial file.
        +
        + +

        + At the top, like before, you see some run-specific data, which probably isn't that useful to you. + (The debug output, and profiling data, + could all help the developers track down any problems you have). + On the right, there is a plot. + This plot shows where the original expression, and Herbie's result, have error. + The horizontal axis is the input; in this case, it is the value of x, + and if your expression has multiple input variables, you'll see several plots, + one for each input variable. + Note that this axis is logarithmic; + one is about halfway between zero and infinity. + The vertical axis is the error, in number of bits; it ranges from 0 at the bottom to 64 at the top. + In this example, you can see that the error of the original program (in red) + is large near zero and small far from zero. + On the other hand, Herbie's output (barely visible in blue) is approximately 0 for all inputs. +

        + +

        + Finally, on the left, you have Herbie's derivation of its result. + Usually you skip to the bottom of this and look at the result: + (λ (x) (+ (* x x) (* x 2))), or x2 + 2 x. + But sometimes it is useful to look at the derivation. + In my case, the derivation uses simplification + to expand the expression into (2 + x) x, + then uses Taylor expansion to expand this into 1 x2 + 2 x, + and then simplifies again to get rid of the unnecessary multiplication by 1. + Again, you usually don't care about this derivation, + but when a result seems strange, it can be helpful to look at the derivation. +

        + +

        Next Steps

        + +

        + If you've made it this far, you've now run Herbie and know how to read its output. + The next step is to use it on some more realistic programs. + Pick a floating point expression you care about and try it out, + or check out Part 2 of the tutorial, + which walks through a more-realistic example. +

        + + + diff --git a/www/doc/1.2/using-cli.html b/www/doc/1.2/using-cli.html new file mode 100644 index 000000000..08e62a91d --- /dev/null +++ b/www/doc/1.2/using-cli.html @@ -0,0 +1,128 @@ + + + + + Using Herbie from the Command Line + + + +
        + +

        Using Herbie from the Command Line

        +
        + +

        + Herbie rewrites floating point expressions to + make them more accurate. The expressions could come from + anywhere—your source code, mathematical papers, or even the output + of Herbgrind, our tool for + finding inaccurate expressions in binaries. This tutorial runs + Herbie on the benchmark programs that Herbie ships with. +

        + +

        Herbie can be used from the command-line + or from the browser. This page covers + using Herbie from the command line.

        + +

        Input expressions

        + +

        + Herbie ships a collection of benchmarks in its bench/ + directory. For example, bench/tutorial.fpcore + contains the following code: +

        + +
        (FPCore (x)
        +  :name "Cancel like terms"
        +  (- (+ 1 x) x))
        +
        +(FPCore (x)
        +  :name "Expanding a square"
        +  (- (sqr (+ x 1)) 1))
        +
        +(FPCore (x y z)
        +  :name "Commute and associate"
        +  (- (+ (+ x y) z) (+ x (+ y z))))
        + +

        This code defines three floating point expressions that we want + to run Herbie on:

        + +
          +
        • (1 + x) - x, titled “Cancel like terms”
        • +
        • (x + 1)² - 1, titled “Expanding a square”
        • +
        • ((x + y) + z) - (x + (y + z)), titled “Commute + and associate”
        • +
        + +

        You can check out our input format + documentation for more about the Herbie input format.

        + +

        The Herbie shell

        + +

        + The Herbie shell lets you interact with Herbie, typing in + benchmark expressions and seeing the outputs. Run the Herbie + shell: +

        + +
        herbie shell
        + +

        + After a few seconds, Herbie will start up and wait for input: +

        + +
        $ herbie shell
        +Herbie 1.2 with seed #(891614428 1933754021 544017565 2852994348 404070416 672462396)
        +Find help on , exit with Ctrl-D
        +herbie> 
        + +

        + The printed seed can be used to reproduce a Herbie run. You can + now paste inputs directly into your terminal for Herbie to + improve: +

        + +
        (FPCore (x) :name "Cancel like terms" (- (+ 1 x) x))
        +(FPCore (x) ... 1)
        + +

        + Interactive use is helpful if you want to play with different + expressions and try multiple variants, informed by Herbie's + advice. Note that Herbie will print a variety of additional + information (like its error estimates and how long it took to + process your input) in the ... portion of the output. +

        + +

        Batch processing FPCore

        + +

        + Alternatively, you can run Herbie on a file with multiple + expressions in it, producing the output expressions to a file. + This mode is intended for use by scripts. +

        + +
        $ herbie improve bench/tutorial.fpcore out.fpcore
        +Seed: 921081490
        +  1/3   [ 1563.552ms]	Cancel like terms	(29→ 0)
        +  2/3   [ 4839.121ms]	Expanding a square	(38→ 0)
        +  3/3   [ 3083.238ms]	Commute and associate	( 0→ 0)
        + +

        + The output file out.fpcore contains more accurate + versions of each program: +

        + +
        ;; seed: #(3123212801 2137904229 2993294009 3035080405 3708006222 26032508)
        +
        +(FPCore (x) 1)
        +(FPCore (x) (* (+ 2 x) x))
        +(FPCore (x y z) 0)
        + +

        + Note that the order of expressions is identical. + For more control over Herbie, see the documentation of + Herbie's command-line options. +

        + + + diff --git a/www/doc/1.2/using-web.html b/www/doc/1.2/using-web.html new file mode 100644 index 000000000..42b6be500 --- /dev/null +++ b/www/doc/1.2/using-web.html @@ -0,0 +1,138 @@ + + + + + Using Herbie from the Browser + + + +
        + +

        Using Herbie from the Browser

        +
        + +

        + Herbie rewrites floating point expressions to + make them more accurate. The expressions could come from + anywhere—your source code, mathematical papers, or even the output + of Herbgrind, our tool for + finding inaccurate expressions in binaries. This tutorial runs + Herbie on the benchmark programs that Herbie ships with. +

        + +

        Herbie can be used from the + command-line and from the browser. This page covers + using Herbie from the browser.

        + + +

        The Herbie web shell

        + +

        + The Herbie web shell lets you interact with Herbie through your + browser, featuring a convenient input format. Run the Herbie web + shell: +

        + +
        herbie web
        + +

        + After a few seconds, the web shell will rev up and direct your + browser to the main web shell page: +

        + +
        $ herbie web
        +Your Web application is running at http://localhost:8000/.
        +Stop this program at any time to terminate the Web Server.
        + +
        + +
        The input page for the web shell.
        +
        + +

        + As in the screenshot, you can type expressions, in standard + mathematical syntax (parsed + by Math.js), and + hit Enter to have Herbie attempt to improve them. +

        + +
        + +
        Herbie improvement in progress.
        +
        + +

        + The web shell will print Herbie's progress, and redirect to a + report once Herbie is done. +

        + +

        + Interactive use of the web shell is the friendliest and easiest + way to use Herbie. The web shell has many + options, including automatically saving the generated reports. +

        + + +

        Batch report generation

        + +

        A report can also be generated directly + from a file of input expressions:

        + +
        $ herbie report input.fpcore output/
        +Starting Herbie on 3 problems...
        +Seed: 921081490
        +  1/3	[ 7108.190ms]	(39→ 0)	Expanding a square
        +  2/3	[ 1894.348ms]	( 0→ 0)	Commute and associate
        +  3/3	[ 873.3889ms]	(29→ 0)	Cancel like terms
        + +

        + This command asks Herbie to generate a report from the input + expressions in input.fpcore and save the report in + the directory output/, which ought not exist yet. + The printed seed can be used to reproduce a run of Herbie. +

        + +

        + Once generated, open the output/report.html page + in your favorite browser (but see the FAQ + if you're using Chrome). From that page, you can click on the rows + in the table at the bottom to see the report for that expression. +

        + +

        Batch report generation is the most informative way to run Herbie + on a large collection of inputs. Like the web shell, it can be + customized through command-line options, + including running Herbie in multiple threads at once.

        + + +

        Input expressions

        + +

        An example input file can be found in bench/tutorial.fpcore:

        + +
        (FPCore (x)
        +  :name "Cancel like terms"
        +  (- (+ 1 x) x))
        +
        +(FPCore (x)
        +  :name "Expanding a square"
        +  (- (* (+ x 1) (+ x 1)) 1))
        +
        +(FPCore (x y z)
        +  :name "Commute and associate"
        +  (- (+ (+ x y) z) (+ x (+ y z))))
        + +

        This code defines three floating point expressions that we want + to run Herbie on:

        + +
          +
        • (1 + x) - x, titled “Cancel like terms”
        • +
        • (x + 1)² - 1, titled “Expanding a square”
        • +
        • ((x + y) + z) - (x + (y + z)), titled “Commute + and associate”
        • +
        + +

        You can check out our input format + documentation for more about the Herbie input format.

        + + + diff --git a/www/doc/1.2/web-main.png b/www/doc/1.2/web-main.png new file mode 100644 index 000000000..8d3010690 Binary files /dev/null and b/www/doc/1.2/web-main.png differ diff --git a/www/doc/1.2/web-progress.png b/www/doc/1.2/web-progress.png new file mode 100644 index 000000000..dbd57cb1c Binary files /dev/null and b/www/doc/1.2/web-progress.png differ diff --git a/www/index.html b/www/index.html index dc4cebc0a..2ae7b8ce9 100644 --- a/www/index.html +++ b/www/index.html @@ -3,7 +3,7 @@ Herbie: Automatically Improving Floating Point Accuracy - + @@ -18,8 +18,7 @@

        Use

        @@ -27,12 +26,11 @@

        Use

        @@ -61,32 +59,20 @@

        Contribute

        -

        Tools

        - -

        - The Herbie project provides Herbie and Herbgrind, complementary tools for finding and fixing floating point problems: -

        - -
        -
        Herbie
        -
        rewrites floating point expressions to make them more accurate. - Herbie supports all commonly-used floating point functions, - and uses a cutting-edge search - to identify more-accurate rearrangements of a floating point computation. -
        -
        Herbgrind
        -
        analyzes binaries to catch floating point inaccuracies as they occur and extract them for analysis. - Herbgrind analyzes binaries directly, - detecting problems on realistic workloads - and extracting them in a standard format. -
        -
        +

        Documentation

        -

        - The Herbie tools have been used on large numerical computations, - mathematical libraries, graphics programs, and embedded systems. - It regularly finds subtle floating point issues and produces fixes. -

        +