Repository: uwplse/herbie Branch: main Commit: c01a0a12a04d Files: 368 Total size: 19.5 MB Directory structure: gitextract_znglj2cb/ ├── .dockerignore ├── .fmt.rkt ├── .github/ │ └── workflows/ │ ├── distribute.yml │ ├── plugins.yml │ ├── release.yml │ ├── resyntax-autofixer.yml │ ├── tests.yml │ └── unit-test.yml ├── .gitignore ├── AGENTS.md ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── bench/ │ ├── .gitignore │ ├── arrays/ │ │ ├── basic.fpcore │ │ ├── ops.fpcore │ │ └── outputs.fpcore │ ├── demo.fpcore │ ├── graphics/ │ │ ├── fidget/ │ │ │ ├── bear.fpcore │ │ │ ├── colonnade.fpcore │ │ │ ├── gyroid-sphere.fpcore │ │ │ ├── hi.fpcore │ │ │ ├── prospero.fpcore │ │ │ └── quarter.fpcore │ │ ├── lod.fpcore │ │ ├── log-transform.fpcore │ │ └── pbrt.fpcore │ ├── hamming/ │ │ ├── machine-decide.fpcore │ │ ├── overflow-underflow.fpcore │ │ ├── quadratic.fpcore │ │ ├── rearrangement.fpcore │ │ ├── series.fpcore │ │ └── trigonometry.fpcore │ ├── haskell.fpcore │ ├── libraries/ │ │ ├── fast-math.fpcore │ │ ├── jmatjs.fpcore │ │ ├── mathjs/ │ │ │ ├── arithmetic.fpcore │ │ │ ├── complex.fpcore │ │ │ ├── probability.fpcore │ │ │ └── trigonometry.fpcore │ │ ├── octave/ │ │ │ ├── CollocWt.fpcore │ │ │ └── randgamma.fpcore │ │ └── rust.fpcore │ ├── mathematics/ │ │ ├── arvind.fpcore │ │ ├── beta-distribution.fpcore │ │ ├── dirichlet-mixture-model.fpcore │ │ ├── excel.fpcore │ │ ├── gui.fpcore │ │ ├── hyperbolic-functions.fpcore │ │ ├── latlong.fpcore │ │ ├── logistic-regression.fpcore │ │ ├── sarnoff.fpcore │ │ ├── statistics.fpcore │ │ ├── symmetry.fpcore │ │ └── xkcd-expr.fpcore │ ├── numerics/ │ │ ├── conte.fpcore │ │ ├── every-cs.fpcore │ │ ├── fma.fpcore │ │ ├── great-debate.fpcore │ │ ├── hamming-misc.fpcore │ │ ├── kahan.fpcore │ │ ├── libm.fpcore │ │ ├── martel.fpcore │ │ ├── polynomial-cancellation.fpcore │ │ ├── rosa.fpcore │ │ └── rump.fpcore │ ├── physics/ │ │ ├── ballistics.fpcore │ │ ├── dimer-escape.fpcore │ │ ├── gated-magnetic-field.fpcore │ │ ├── gravitation.fpcore │ │ ├── kalman.fpcore │ │ ├── multiphoton-states.fpcore │ │ ├── quantum-walk.fpcore │ │ ├── sidey.fpcore │ │ ├── superfluidity-breakdown.fpcore │ │ ├── tea-flows.fpcore │ │ ├── tea-whistle.fpcore │ │ └── universal-linear-optics.fpcore │ ├── proj/ │ │ ├── krovak.fpcore │ │ ├── omerc.fpcore │ │ ├── som.fpcore │ │ ├── somerc.fpcore │ │ └── tmerc.fpcore │ ├── regression.fpcore │ └── tutorial.fpcore ├── egg-herbie/ │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── info.rkt │ ├── main.rkt │ └── src/ │ ├── lib.rs │ └── math.rs ├── infra/ │ ├── .gitignore │ ├── analyze-rules.rkt │ ├── bench/ │ │ ├── posit-pherbie.fpcore │ │ └── posits.fpcore │ ├── ci.rkt │ ├── convert-demo-json.sh │ ├── convert-demo.rkt │ ├── convert-json.rkt │ ├── coverage.rkt │ ├── diagrams/ │ │ ├── .gitignore │ │ ├── 1.5/ │ │ │ └── herbie-system.drawio │ │ ├── 1.6/ │ │ │ └── herbie-system.drawio │ │ ├── 1.6-changes/ │ │ │ └── herbie-system.drawio │ │ ├── 2.1/ │ │ │ └── herbie-system.drawio │ │ └── 2.2/ │ │ └── system.key │ ├── diff.rkt │ ├── fidget2core.py │ ├── herbie-demo.service │ ├── merge.rkt │ ├── nightly.sh │ ├── softposit.rkt │ ├── sort-fpcore.rkt │ ├── survey/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── seed-variance.sh │ │ └── src/ │ │ ├── plot-results.sh │ │ ├── seed-bar-chart.py │ │ ├── seed-violin-plot.py │ │ ├── test-versus-plot.py │ │ └── test-violin-plot.py │ ├── survey.rkt │ └── test-api.mjs ├── src/ │ ├── api/ │ │ ├── datafile.rkt │ │ ├── demo.rkt │ │ ├── run.rkt │ │ ├── sandbox.rkt │ │ ├── server.rkt │ │ └── shell.rkt │ ├── config.rkt │ ├── core/ │ │ ├── alt-table.rkt │ │ ├── alternative.rkt │ │ ├── arrays.rkt │ │ ├── batch-reduce.rkt │ │ ├── bsearch.rkt │ │ ├── compiler.rkt │ │ ├── derivations.rkt │ │ ├── egg-herbie.rkt │ │ ├── egglog-herbie-tests.rkt │ │ ├── egglog-herbie.rkt │ │ ├── egglog-subprocess.rkt │ │ ├── explain.rkt │ │ ├── localize.rkt │ │ ├── mainloop.rkt │ │ ├── patch.rkt │ │ ├── points.rkt │ │ ├── preprocess.rkt │ │ ├── programs.rkt │ │ ├── prove-rules.rkt │ │ ├── regimes.rkt │ │ ├── rules.rkt │ │ ├── sampling.rkt │ │ ├── searchreals.rkt │ │ ├── taylor.rkt │ │ └── test-rules.rkt │ ├── info.rkt │ ├── main.rkt │ ├── platform.rkt │ ├── platforms/ │ │ ├── c-windows.rkt │ │ ├── c.rkt │ │ ├── herbie10.rkt │ │ ├── herbie20.rkt │ │ ├── julia.rkt │ │ ├── math.rkt │ │ ├── python.rkt │ │ ├── racket.rkt │ │ ├── reflow.rkt │ │ └── rival.rkt │ ├── reports/ │ │ ├── common.rkt │ │ ├── core2mathjs.rkt │ │ ├── data.rkt │ │ ├── history.rkt │ │ ├── make-graph.rkt │ │ ├── pages.rkt │ │ ├── plot.rkt │ │ ├── resources/ │ │ │ ├── 404.html │ │ │ ├── demo.js │ │ │ ├── main.css │ │ │ ├── report-page.js │ │ │ ├── report.css │ │ │ ├── report.html │ │ │ └── report.js │ │ ├── timeline.rkt │ │ └── traceback.rkt │ ├── syntax/ │ │ ├── batch.rkt │ │ ├── float.rkt │ │ ├── generators.rkt │ │ ├── load-platform.rkt │ │ ├── matcher.rkt │ │ ├── platform-language.rkt │ │ ├── platform.rkt │ │ ├── read.rkt │ │ ├── rival.rkt │ │ ├── sugar.rkt │ │ ├── syntax-check.rkt │ │ ├── syntax.rkt │ │ ├── test-syntax.rkt │ │ ├── type-check.rkt │ │ └── types.rkt │ └── utils/ │ ├── common.rkt │ ├── dvector.rkt │ ├── errors.rkt │ ├── multi-command-line.rkt │ ├── pareto.rkt │ ├── pretty-print.rkt │ ├── profile.rkt │ └── timeline.rkt └── www/ ├── aec.html ├── demo.js ├── doc/ │ ├── 0.9/ │ │ ├── docker.html │ │ ├── input.html │ │ ├── installing-herbgrind.html │ │ ├── installing-herbie.html │ │ ├── options.html │ │ ├── using-herbgrind.html │ │ └── using-herbie.html │ ├── 1.0/ │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing-herbgrind.html │ │ ├── installing-herbie.html │ │ ├── options.html │ │ ├── release-notes.html │ │ ├── using-herbgrind.html │ │ └── using-herbie.html │ ├── 1.1/ │ │ ├── compare.js │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── results-new.json │ │ ├── results-old.json │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 1.2/ │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 1.3/ │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 1.4/ │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 1.5/ │ │ ├── diagrams.html │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 1.6/ │ │ ├── diagrams.html │ │ ├── docker.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 2.0/ │ │ ├── api-endpoints.html │ │ ├── diagrams.html │ │ ├── docker.html │ │ ├── error.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 2.1/ │ │ ├── api-endpoints.html │ │ ├── diagrams.html │ │ ├── docker.html │ │ ├── error.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ ├── 2.2/ │ │ ├── api-endpoints.html │ │ ├── diagrams.html │ │ ├── docker.html │ │ ├── error.html │ │ ├── faq.html │ │ ├── input.html │ │ ├── installing.html │ │ ├── options.html │ │ ├── platforms.html │ │ ├── plugins.html │ │ ├── release-notes.html │ │ ├── report.html │ │ ├── toc.js │ │ ├── tutorial.html │ │ ├── using-cli.html │ │ └── using-web.html │ └── 2.3/ │ ├── api-endpoints.html │ ├── diagrams.html │ ├── docker.html │ ├── error.html │ ├── faq.html │ ├── input.html │ ├── installing.html │ ├── options.html │ ├── platforms.html │ ├── plugins.html │ ├── release-notes.html │ ├── report.html │ ├── toc.js │ ├── tutorial.html │ ├── using-cli.html │ └── using-web.html ├── doc.html ├── graph.js ├── index.html ├── main.css ├── papers.html ├── pldi15-slides.key └── results.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .git .github .gitignore reports papers egg-herbie/target ================================================ FILE: .fmt.rkt ================================================ #lang racket/base (require fmt/conventions) (provide the-formatter-map) (define (the-formatter-map s) (case s [("define-operators") (standard-formatter-map "begin")] [("define-operations") (standard-formatter-map "define")] [("define-rules") (standard-formatter-map "define")] [("define-generator") (standard-formatter-map "define")] [("define-api-endpoint") (standard-formatter-map "define")] [else #f])) ================================================ FILE: .github/workflows/distribute.yml ================================================ name: Distribute on: push: branches: - main env: RUST_BACKTRACE: full jobs: distribute: strategy: matrix: include: - os: macos-latest arch: aarch64 - os: macos-15-intel arch: x64 - os: ubuntu-latest arch: x64 - os: windows-latest arch: x64 runs-on: ${{ matrix.os }} steps: - name: "Install Racket" uses: Bogdanp/setup-racket@v1.14 with: version: "8.18" architecture: ${{ matrix.arch }} - name: Cache Racket dependencies uses: actions/cache@v4 with: key: ${{ runner.os }}-deps path: | ~/.cache/racket ~/.local/share/racket ~/Library/Racket/ ${{ env.APPDATA }}/Racket - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt, clippy - name: Cache cargo build uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v5 - name: "Install dependencies" run: make install # Build executable and remove Herbie launcher - name: "Build standalone executable" run: make distribution - name: "Uninstall Herbie launcher" run: raco pkg remove herbie egg-herbie # Test executable - name: "Test executable, improve tool (Windows)" if: runner.os == 'Windows' run: herbie-compiled/herbie.exe improve --threads yes bench/tutorial.fpcore improve.fpcore - name: "Test executable, improve tool (Linux / MacOS)" if: runner.os != 'Windows' run: herbie-compiled/bin/herbie improve --threads yes bench/tutorial.fpcore improve.fpcore - name: "Test executable, report tool (Windows)" if: runner.os == 'Windows' run: herbie-compiled/herbie.exe report --threads yes bench/tutorial.fpcore tmp - name: "Test executable, report tool (Linux / MacOS)" if: runner.os != 'Windows' run: herbie-compiled/bin/herbie report --threads yes bench/tutorial.fpcore tmp ================================================ FILE: .github/workflows/plugins.yml ================================================ name: Plugins on: [push] env: RUST_BACKTRACE: full jobs: softposit: name: "Plugin tests (Posits)" runs-on: ubuntu-latest steps: - name: "Install Packages" run: sudo apt-get install -y libmpfr6 libmpfr-dev - name: "Install Racket" uses: Bogdanp/setup-racket@v1.14 with: version: "8.18" - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt, clippy - name: Cache cargo build uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v5 - name: "Install dependencies" run: make install - name: "Install SoftPosit support" run: raco pkg install softposit-rkt - name: "Run posit benchmarks" run: racket infra/ci.rkt --rival2 --platform infra/softposit.rkt --precision posit16 --seed 0 infra/bench/posits.fpcore ================================================ FILE: .github/workflows/release.yml ================================================ name: Build new Herbie release on: push: tags: - 'v*' jobs: build: name: Build strategy: matrix: # manual matrix include: - os: windows-latest os-name: windows arch: x64 - os: ubuntu-20.04 # keep old for glibc compatability os-name: ubuntu arch: x64 - os: macos-latest os-name: macOS-m1 arch: arm64 - os: macos-15-intel os-name: macOS arch: x64 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 - name: Install Racket uses: Bogdanp/setup-racket@v1.14 with: version: 8.18 architecture: ${{ matrix.arch }} - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable - name: build egg-herbie run: cargo build --release --manifest-path=egg-herbie/Cargo.toml - name: Create tarball run: raco pkg create --format zip egg-herbie - name: Rename tarballs run: | mv egg-herbie.zip egg-herbie-${{ matrix.os-name }}.zip mv egg-herbie.zip.CHECKSUM egg-herbie-${{ matrix.os-name }}.zip.CHECKSUM - name: Upload pre-built egg-herbie uses: actions/upload-artifact@v4 with: path: egg-herbie-${{ matrix.os-name }}.zip name: egg-herbie-${{ matrix.os-name }}.zip if-no-files-found: error - name: Upload pre-built egg-herbie checksum uses: actions/upload-artifact@v4 with: path: egg-herbie-${{ matrix.os-name }}.zip.CHECKSUM name: egg-herbie-${{ matrix.os-name }}.zip.CHECKSUM if-no-files-found: error release: name: Create Initial Release runs-on: ubuntu-latest needs: build steps: - name: Download pre-built artifacts uses: actions/download-artifact@v4 with: path: artifacts pattern: egg-herbie-* merge-multiple: true - name: Create Release uses: ncipollo/release-action@v1.20.0 with: tag: ${{ github.ref }} name: ${{ github.ref }} commit: ${{ github.commit }} draft: true prerelease: false artifactErrorsFailBuild: true artifacts: "artifacts/*" ================================================ FILE: .github/workflows/resyntax-autofixer.yml ================================================ name: Resyntax Autofixer on: workflow_dispatch: schedule: - cron: "0 0 * * 0" jobs: autofix: runs-on: ubuntu-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} permissions: pull-requests: write contents: write steps: - name: "Install Packages" run: sudo apt-get install -y libmpfr6 libmpfr-dev - name: "Install Racket" uses: Bogdanp/setup-racket@v1.14 with: version: current - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt, clippy - name: Cache cargo build uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v5 - name: "Install dependencies" run: make install - name: Create a Resyntax pull request uses: jackfirth/create-resyntax-pull-request@v0.5.1 with: private-key: ${{ secrets.RESYNTAX_APP_PRIVATE_KEY }} max-fixes: '10' max-modified-files: '3' max-modified-lines: '100' ================================================ FILE: .github/workflows/tests.yml ================================================ name: Integration on: [push] env: RUST_BACKTRACE: full jobs: hamming: name: "Integration tests (Hamming)" runs-on: ubuntu-latest strategy: matrix: racket-version: [ '8.7', '8.18' ] precision: [ 'binary32', 'binary64' ] steps: - name: "Install Packages" run: sudo apt-get install -y libmpfr6 libmpfr-dev - name: "Install Racket" uses: Bogdanp/setup-racket@v1.14 with: version: ${{ matrix.racket-version }} - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt, clippy - name: Cache cargo build uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v5 - name: "Install dependencies" run: make install - run: racket -y infra/ci.rkt --precision ${{ matrix.precision }} --seed 0 bench/hamming/ ================================================ FILE: .github/workflows/unit-test.yml ================================================ name: Unit tests on: [push] env: RUST_BACKTRACE: full jobs: unit-tests: name: "Unit Tests" runs-on: ubuntu-latest steps: - name: "Install Packages" run: sudo apt-get install -y libmpfr6 libmpfr-dev - name: "Install Racket" uses: Bogdanp/setup-racket@v1.14 with: version: "8.18" - name: Install Rust compiler uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt, clippy - name: Cache cargo build uses: Swatinem/rust-cache@v2 - uses: actions/checkout@v5 - name: "Install dependencies" run: make install # SoftPosit is required to test the softposit platform we use for testing - name: "Install SoftPosit support" run: raco pkg install softposit-rkt - name: "Test raco fmt compliance" run: make fmt && git diff --exit-code # Run the Herbie unit tests - name: "Run Herbie unit tests" run: raco test src/ infra/ egg-herbie/ # Test the command-line tools - name: "Test the shell command-line tool" run: | /tmp/out.fpcore test `grep -c :precision /tmp/out.fpcore` -eq 3 test `grep -c ';;' /tmp/out.fpcore` -eq 0 - name: "Test the improve command-line tool" run: | racket -l herbie improve bench/tutorial.fpcore /tmp/out.fpcore test `grep -c :precision /tmp/out.fpcore` -eq 3 test `grep -c '^; ' /tmp/out.fpcore` -eq 0 - name: "Run the report command-line tool" run: | racket -l herbie report bench/tutorial.fpcore /tmp/out/ test -d /tmp/out/ test -f /tmp/out/index.html test -f /tmp/out/results.json - name: "Run two reports with the same seed and diff them" run: | racket -l herbie report --threads yes --seed 1 bench/hamming/rearrangement.fpcore tmp-0 racket -l herbie report --threads yes --seed 1 bench/hamming/rearrangement.fpcore tmp-1 racket infra/diff.rkt tmp-0 tmp-1 - name: "Run Herbie on array benchmarks" run: racket -l herbie improve bench/arrays /tmp/out.fpcore - name: "Run Herbie with egglog" run: racket -y infra/ci.rkt --egglog --precision binary64 --seed 1 bench/tutorial.fpcore # Test the egg-herbie Rust code - run: cd egg-herbie && cargo clippy --tests continue-on-error: true - run: cd egg-herbie && cargo test - run: cd egg-herbie && cargo fmt -- --check - run: cd egg-herbie && raco test ./ # Test the API - uses: actions/setup-node@v6 with: node-version: 20 - name: "Test the endpoint" run: node infra/test-api.mjs ================================================ FILE: .gitignore ================================================ # OS cruft *~ *.swp .DS_Store # Standard directories www/demo demo.log test.fpcore tmp tmp-* dump-* .codex # Related repositories /reports papers plugins plugin rival rival3 regraph egg-herbie/pkg odyssey generic-flonum # Racket *.zo *.dep herbie-compiled/ # Python .env/ .worktrees ================================================ FILE: AGENTS.md ================================================ # Formatting - Use `map` over `for/list` only if it avoids a `lambda`. - Always use `in-list` and similar with `for` variants. - Always pass `#:length N` to `for/vector`. - Do not code defensively. Do not do runtime type checks or fallback. Prefer to examine all callers to establish types, or if they can differ prefer `match` with explicit patterns for all cases, to ensure that an unanticipated values at any point cause errors. - Format Racket code with `make fmt` at the top level. - Check `git diff` and delete dead code before finishing a task. - Update docs in `www/doc/2.3/` if you change user-visible options. - Enforce module layering `utils < syntax < core < reports < api`: do not add imports from a lower layer to a higher layer. # Testing - If you change the Herbie core, test that your changes work with `racket src/main.rkt report bench/tutorial.fpcore tmp`. This should take about 5-10 seconds and all of the tests should pass with perfect accuracy. You can also use other benchmark suites if asked. - If you need to store two reports name the folders `tmp-X` for some X. - Arguments come after the word `report` before any other arguments. - Herbie prints a seed every time it runs; you can pass `--seed N` after the "report" argument to fix the seed reproducibly. - You can pass `--timeout T` to time out a benchmark after T seconds. - After running tests, `tmp` will have one directory per benchmark. Each has a detailed `graph.html`, including tracebacks for crashes. - The default e-graph backend is `egg`; pass `--enable generate:egglog` to enable the `egglog` backend. You may need to add `~/.cargo/bin` to the `PATH`. - Some files have unit tests; run them with `raco test `. # Observability - `jq` is installed, use it - If you're investigating a single benchmark, copy it to a file named `test.fpcore` and run just that file. - Herbie runs output a `tmp//timeline.json` with rich observability data for each benchmark. - A timeline is a list of phases, each a map from key to "table", each table is a list of fixed-length arrays. - Timeline types are defined in `src/utils/timeline.rkt`; HTML generation is defined in `src/reports/timeline.rkt`. - Add to the timeline with `(timeline-push! 'type val1 val2 ...)`. The `val`s must be JSON-compatible, so convert symbols to strings. - Herbie runs generates a profile in `tmp//profile.json`. - You can also dump GC/memory data to `dump-trace.json` with `--enable dump:trace`, Rival commands with `--enable dump:rival`, and egglog commands with `--enable dump:egglog`. Dumps go in `dump-XXX` in the current directory. Clean that directory when done. ================================================ FILE: Dockerfile ================================================ # Multistage build: https://docs.docker.com/develop/develop-images/multistage-build/ # We build necessary binaries in one image, then COPY them to a second production # image to save some space. # Build image # Builds output under /herbie/egg-herbie FROM rust:1.88.0 AS egg-herbie-builder WORKDIR /herbie COPY egg-herbie egg-herbie RUN cargo build --release --manifest-path=egg-herbie/Cargo.toml RUN cargo install egglog --version 1.0.0 # Production image FROM racket/racket:8.17-full AS production LABEL maintainer="Pavel Panchekha " COPY --from=egg-herbie-builder /herbie/egg-herbie /src/egg-herbie RUN raco pkg install --no-docs /src/egg-herbie COPY --from=egg-herbie-builder /usr/local/cargo/bin/egglog /usr/local/bin/egglog COPY src /src/herbie RUN raco pkg install --no-docs --auto /src/herbie ENTRYPOINT ["racket", "/src/herbie/main.rkt"] EXPOSE 80 # NOTE --public allows the Docker host to interact with the demo, # typical users shouldn't need to use it. CMD ["web", "--public", "--port", "80", "--quiet", "--demo", "--threads", "2"] ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2015-2024 Herbie Project Modified work Copyright 2016 Google Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .PHONY: help install egg-herbie nightly index start-server deploy coverage help: @echo "Type 'make install' to install Herbie" @echo "Then type 'racket -l herbie web' to run it." install: clean egg-herbie egglog-herbie update clean: raco pkg remove --force --no-docs herbie && echo "Uninstalled old herbie" || : raco pkg remove --force --no-docs egg-herbie && echo "Uninstalled old egg-herbie" || : raco pkg remove --force --no-docs egg-herbie-linux && echo "Uninstalled old egg-herbie" || : raco pkg remove --force --no-docs egg-herbie-windows && echo "Uninstalled old egg-herbie" || : raco pkg remove --force --no-docs egg-herbie-osx && echo "Uninstalled old egg-herbie" || : raco pkg remove --force --no-docs egg-herbie-macosm1 && echo "Uninstalled old egg-herbie" || : update: raco pkg install --update-deps --no-docs --auto --name herbie src/ raco pkg update --auto rival raco pkg update --auto rival3 raco pkg update --name herbie --deps search-auto src/ egg-herbie: cargo build --release --manifest-path=egg-herbie/Cargo.toml raco pkg remove --force --no-docs egg-herbie && echo "Warning: uninstalling egg-herbie and reinstalling local version" || : raco pkg remove --force --no-docs egg-herbie-linux && echo "Warning: uninstalling egg-herbie and reinstalling local version" || : raco pkg remove --force --no-docs egg-herbie-windows && echo "Warning: uninstalling egg-herbie and reinstalling local version" || : raco pkg remove --force --no-docs egg-herbie-osx && echo "Warning: uninstalling egg-herbie and reinstalling local version" || : raco pkg remove --force --no-docs egg-herbie-macosm1 && echo "Warning: uninstalling egg-herbie and reinstalling local version" || : raco pkg install ./egg-herbie egglog-herbie: cargo install --git "https://github.com/egraphs-good/egglog-experimental" --branch main egglog-experimental distribution: minimal-distribution cp -r bench herbie-compiled/ minimal-distribution: mkdir -p herbie-compiled/ cp README.md herbie-compiled/ cp LICENSE.md herbie-compiled/ cp logo.png herbie-compiled/ raco exe -o herbie --orig-exe --embed-dlls --vv src/main.rkt [ ! -f herbie.exe ] || (raco distribute herbie-compiled herbie.exe && rm herbie.exe) [ ! -f herbie.app ] || (raco distribute herbie-compiled herbie.app && rm herbie.app) [ ! -f herbie ] || (raco distribute herbie-compiled herbie && rm herbie) nightly: bash infra/nightly.sh bench reports --threads 2 upgrade: git pull $(MAKE) install start-server: racket -y src/main.rkt web --seed 1 --timeout 150 --threads 8 \ --demo --public --prefix /demo/ --port 4053 --save-session www/demo/ \ --log infra/server.log --quiet 2>&1 fmt: @raco fmt -i $(shell find egg-herbie src infra -name '*.rkt' -not -path 'src/platforms/*.rkt' -not -path 'infra/softposit.rkt') coverage: @racket infra/coverage.rkt --benchmark $(or $(BENCH),bench/hamming) $(ARGS) herbie.zip herbie.zip.CHECKSUM: raco pkg create src/ mv src.zip herbie.zip mv src.zip.CHECKSUM herbie.zip.CHECKSUM random-file: @find src infra -path '*/compiled/*' -prune \ -o -path 'infra/survey/*' -prune \ -o -type f -print | \ sort -R | head -n1 ================================================ FILE: README.md ================================================ ![Herbie](logo.png) Herbie automatically improves the error of floating point expressions. Visit [our website](https://herbie.uwplse.org) for tutorials, documentation, and an online demo. Herbie is a joint project of the Universities of [Washington](https://uwplse.org) and [Utah](https://cpu.cs.utah.edu). ## Installing We recommend installing Herbie from the Racket Package Archive. To do so, [install Racket](https://download.racket-lang.org/) and then run: raco pkg install --auto herbie You can then run `racket -l herbie` to run Herbie. Herbie supports Windows, Linux, and macOS on both x86 and AArch64. For full instructions, see the [documentation](https://herbie.uwplse.org/doc/latest/installing.html). ## Installing from Source You can install Herbie from source if you want to participate in Herbie development. This requires [Racket](https://download.racket-lang.org/) (8.0 or later) and [Rust](https://www.rust-lang.org/tools/install) (1.87.0 or later). On Linux, avoid the Snap installer for Racket. Then, download the this repository and run: make install You can then run `racket -l herbie` to run Herbie, or run `src/main.rkt` directly. ## Running Herbie You can run Herbie's web interface with: $ racket -l herbie web For more information on running Herbie, please see the [tutorial](https://herbie.uwplse.org/doc/latest/using-web.html). You can also use Herbie from the command line: $ racket -l herbie shell Herbie 1.3 with seed 1866513483 Find help on https://herbie.uwplse.org/, exit with Ctrl-D herbie> (FPCore (x) (- (+ 1 x) x)) (FPCore (x) ... 1) Here the input is the program `(1 + x) - x` and the output is `1`. The input format is [FPCore](https://fpbench.org/spec/fpcore-1.2.html); you can see more examples in `bench/`. Besides `shell`, Herbie has batch the `improve` and `report` commands. The [documentation](https://herbie.uwplse.org/doc/latest/options.html) has more details. ================================================ FILE: bench/.gitignore ================================================ random.rkt ================================================ FILE: bench/arrays/basic.fpcore ================================================ ; -*- mode: scheme -*- (FPCore first-element ((v 2)) :name "First element from 2-element vector input" (ref v 0)) (FPCore nested-literal () :name "Element from nested array literal" (ref (ref (array (array 2.0 0.5) (array -0.25 1.5)) 1) 0)) (FPCore bias-add () :name "Sum two literal bias terms" (let* ([bias (array 0.1 -0.2)]) (+ (ref bias 0) (ref bias 1)))) ================================================ FILE: bench/arrays/ops.fpcore ================================================ ; -*- mode: scheme -*- (FPCore vector-sum ((v 2)) :name "Sum two-element vector" :pre (and (<= (fabs (ref v 0)) 1e150) (<= (fabs (ref v 1)) 1e150)) (+ (ref v 0) (ref v 1))) (FPCore dot-product-2d ((a 2) (b 2)) :name "2D vector dot product" :pre (and (<= (fabs (ref a 0)) 1e150) (<= (fabs (ref a 1)) 1e150) (<= (fabs (ref b 0)) 1e150) (<= (fabs (ref b 1)) 1e150)) (+ (* (ref a 0) (ref b 0)) (* (ref a 1) (ref b 1)))) (FPCore weighted-combo (w (v 2)) :name "Weighted combination of scalar and vector" :pre (and (<= (fabs w) 1e150) (<= (fabs (ref v 0)) 1e150) (<= (fabs (ref v 1)) 1e150)) (+ (* w (ref v 0)) (ref v 1))) (FPCore scaled-sum ((v 2) s) :name "Scaled sum of vector elements" :pre (and (<= (fabs s) 1e150) (<= (fabs (ref v 0)) 1e150) (<= (fabs (ref v 1)) 1e150)) (+ (* s (ref v 0)) (* s (ref v 1)))) (FPCore mat-vec-first-row ((v 2)) :name "Apply fixed 2x2 matrix row to vector" :pre (and (<= (fabs (ref v 0)) 1e150) (<= (fabs (ref v 1)) 1e150)) (let* ([row0 (array 1.5 -0.75)] [row1 (array -0.1 0.3)]) (+ (* (ref row0 0) (ref v 0)) (* (ref row0 1) (ref v 1))))) ================================================ FILE: bench/arrays/outputs.fpcore ================================================ ; -*- mode: scheme -*- (FPCore literal-vector () :name "Return fixed 2-element array literal" :precision binary64 (array 1.0 -1.0)) (FPCore copy-vector ((v 2)) :name "Return input vector elements as array" :precision binary64 (array (ref v 0) (ref v 1))) (FPCore ((x 2)) :name "2sin (example 3.3) using vector" :pre (and (<= -1e4 (ref x 0)) (<= (ref x 0) 1e4) (< (* 1e-16 (fabs (ref x 0))) (ref x 1)) (< (ref x 1) (* (fabs (ref x 0))))) (- (sin (+ (ref x 0) (ref x 1))) (sin (ref x 0)))) (FPCore create-vector ((! :precision binary64 v0) (! :precision binary64 v1)) :name "Return input elements as array" :precision binary64 (array v0 v1)) (FPCore reverse-vector ((v 2)) :name "Reverse input vector elements" :precision binary64 (array (ref v 1) (ref v 0))) (FPCore mean-and-span ((v 2)) :name "Mean and half-difference of vector" :precision binary64 :pre (and (<= (fabs (ref v 0)) 1e150) (<= (fabs (ref v 1)) 1e150)) (let* ([mean (* 0.5 (+ (ref v 0) (ref v 1)))] [half-span (* 0.5 (- (ref v 0) (ref v 1)))]) (array mean half-span))) (FPCore duplicate-scalar ((! :precision binary64 s)) :name "Duplicate scalar into array" :precision binary64 :pre (<= (fabs s) 1e150) (array s s)) (FPCore pairwise-sum ((a 2) (b 2)) :name "Pairwise sum of two input vectors" :precision binary64 (array (+ (ref a 0) (ref b 0)) (+ (ref a 1) (ref b 1)))) ================================================ FILE: bench/demo.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x y z) :name "fabs fraction 1" (fabs (- (/ (+ x 4) y) (* (/ x y) z)))) (FPCore (a b) :name "fabs fraction 2" (/ (fabs (- a b)) 2)) (FPCore (f n) :name "subtraction fraction" (/ (- (+ f n)) (- f n))) (FPCore (x) :name "exp neg sub" (exp (- (- 1 (* x x))))) (FPCore (x) :name "sqrt times" (* (sqrt (- x 1)) (sqrt x))) (FPCore (x) :name "neg log" (- (log (- (/ 1 x) 1)))) (FPCore (x) :name "sqrt sqr" :alt (! :herbie-platform default (if (< x 0) 2 0)) (- (/ x x) (* (/ 1 x) (sqrt (* x x))))) (FPCore (a b c) :name "jeff quadratic root 1" (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 (- (* b b) (* (* 4 a) c))))) (if (>= b 0) (/ (* 2 c) (- (- b) d)) (/ (+ (- b) d) (* 2 a))))) (FPCore (p) :name "peicewise-defined" :pre (and (>= p -1.79e308) (<= p 1.79e308)) :alt (if (and (>= p -1e-6) (<= p 0)) (- (+ p 1) (/ (* p p) (- p 1))) (- p (fma (/ p (- p 1)) p -1))) (- (+ p 1.0) (/ (pow (fmin p 0.0) 2.0) (- (fmin p 0.0) 1.0)))) (FPCore (x) :name "(x - 1) to (x - 20)" :precision binary64 :pre (<= 1 x 20) (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (- x 1.0) (- x 2.0)) (- x 3.0)) (- x 4.0)) (- x 5.0)) (- x 6.0)) (- x 7.0)) (- x 8.0)) (- x 9.0)) (- x 10.0)) (- x 11.0)) (- x 12.0)) (- x 13.0)) (- x 14.0)) (- x 15.0)) (- x 16.0)) (- x 17.0)) (- x 18.0)) (- x 19.0)) (- x 20.0))) (FPCore (a b C) :name "Law of Cosines" :precision binary64 :pre (and (<= 0 a 1e9) (<= 0 b 1e9) (<= 0 C PI)) (sqrt (- (+ (pow a 2.0) (pow b 2.0)) (* (* (* 2.0 a) b) (cos C))))) (FPCore (a b c) :name "Q^3 (Cubic Equation Discriminant Part)" :precision binary64 :pre (and (<= -1e9 a 1e9) (<= -1e9 b 1e9) (<= -1e9 c 1e9)) (pow (/ (- (* 3.0 (* a c)) (pow b 2.0)) (* 9.0 (pow a 2.0))) 3.0)) (FPCore (x a) :name "Success Probability" :precision binary64 :pre (and (<= 0.0 x 1) (<= 1/3 a 3.0)) (- 1.0 (pow (- 1.0 x) a))) (FPCore (x) :name "Quantum aproximation with lots of constants" :precision binary64 (let* ( (E (exp 1)) (sqrtE (sqrt E)) (invSqrtE (/ 1 sqrtE)) (logTerm (log (- 1 invSqrtE))) (log2Term (* logTerm logTerm)) (log3Term (* log2Term logTerm)) (E3/2 (pow E 1.5)) (E5/2 (pow E 2.5)) (E7/2 (pow E 3.5)) (dx (- x 1/2)) ;; Common denominator (D (+ (* sqrtE log2Term) (* 16 sqrtE logTerm) (* -64 sqrtE) (* -8 (pow E 2) log2Term) (* -4 (pow E 2) logTerm) (* -8 E log2Term) (* -84 E logTerm) (* 16 E) (* 2 E3/2 log2Term) (* 16 E3/2 logTerm) (* -4 E3/2) (* E5/2 log2Term) -24)) ;; Numerators (n1 (+ (* sqrtE log3Term) (* 20 sqrtE log2Term) (* 210 sqrtE logTerm) (* -1200 sqrtE) (* -18 (pow E 3) log3Term) (* -20 (pow E 3) log2Term) (* -116 (pow E 2) log3Term) (* -720 (pow E 2) log2Term) (* 120 (pow E 2) logTerm) (* -18 E log3Term) (* -220 E log2Term) (* -1280 E logTerm) (* -300 E) (* 3 E3/2 log3Term) (* -20 E3/2 log2Term) (* -930 E3/2 logTerm) (* 3 E5/2 log3Term) (* 120 E5/2 log2Term) (* -20 E5/2 logTerm) (* E7/2 log3Term) -120)) (n2 (+ (* sqrtE log3Term) (* 18 sqrtE log2Term) (* -108 sqrtE logTerm) (* -192 sqrtE) (* (pow E 3) log3Term) (* -16 (pow E 2) log3Term) (* 6 (pow E 2) log2Term) (* -18 (pow E 2) logTerm) (* -9 E log3Term) (* -94 E log2Term) (* -378 E logTerm) (* 48 E) (* -16 E3/2 log3Term) (* -174 E3/2 log2Term) (* 72 E3/2 logTerm) (* -12 E3/2) (* -9 E5/2 log3Term) (* -4 E5/2 log2Term) (* -12 logTerm) -72)) (n3 (+ (* sqrtE logTerm) (* -216 sqrtE) (* -8 (pow E 3) logTerm) (* 2 (pow E 3)) (* -176 (pow E 2) logTerm) (* 96 (pow E 2)) (* -8 E logTerm) (* 266 E) (* 83 E3/2 logTerm) (* -232 E3/2) (* 83 E5/2 logTerm) (* -16 E5/2) (* E7/2 logTerm) 12)) (n4 (+ (* -18 sqrtE logTerm) (* -110 sqrtE) (* (pow E 3) logTerm) (* 53 (pow E 2) logTerm) (* 13 E logTerm) (* 30 E) (* -66 E3/2 logTerm) (* 30 E3/2) (* -8 E5/2 logTerm) logTerm 10)) (n5 (+ (* -18 sqrtE log2Term) (* -115 sqrtE logTerm) (* -340 sqrtE) (* (pow E 3) log2Term) (* 3 (pow E 2) log2Term) (* 90 (pow E 2) logTerm) (* -10 (pow E 2)) (* 3 E log2Term) (* 20 E logTerm) (* -390 E) (* -116 E3/2 log2Term) (* -530 E3/2 logTerm) (* 60 E3/2) (* -18 E5/2 log2Term) (* -15 E5/2 logTerm) log2Term (* 10 logTerm) 60)) (n6 (+ (* sqrtE log2Term) (* 15 sqrtE logTerm) (* -156 sqrtE) (* (pow E 3) log2Term) (* -16 (pow E 2) log2Term) (* 30 (pow E 2) logTerm) (* -6 (pow E 2)) (* -9 E log2Term) (* -70 E logTerm) (* -126 E) (* -16 E3/2 log2Term) (* -180 E3/2 logTerm) (* 24 E3/2) (* -9 E5/2 log2Term) (* -7 E5/2 logTerm) -12)) ) (+ 1 (/ (+ (/ (* n1 (* dx dx)) (* 30 (pow (- 1 sqrtE) 2) D)) (/ (* n2 dx) (* 3 (- 1 sqrtE) D)) (/ (* sqrtE n3 (pow dx 4)) (* 360 (pow (- 1 sqrtE) 4) D)) (/ (* sqrtE n4 (pow dx 3)) (* 30 (pow (- 1 sqrtE) 3) D)) (/ (* sqrtE n5 (pow dx 2)) (* 30 (pow (- 1 sqrtE) 2) D)) (/ (* n6 dx) (* 3 (- 1 sqrtE) D)) logTerm) )))) ================================================ FILE: bench/graphics/fidget/bear.fpcore ================================================ (FPCore (x y z) :name "The bear head is based on a design by Hazel Fraticelli and Anthony Taconi" :precision binary64 (fmin (/ (- (log (+ (exp (* -11 (- (/ (- (log (+ (exp (* -30.555555 (/ (- (log (+ (exp (* -11 (/ (- (log (+ (exp (* -11 (- (/ (- (log (+ (exp (* -16 (/ (- (log (+ (exp (* -5.612245 (- (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) -0.4) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.1) 2))) 1.4))) (exp (* -5.612245 (- (sqrt (+ (+ (pow (* (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) (+ 1 (* 2 (exp (- (/ (sqrt (+ (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 2) (pow (+ (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 1.5) 2)) (pow (+ 0.8 (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1))))))) 2))) 1)))))) 2) (pow (- (* (+ (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 1.5) (+ 1 (* 2 (exp (- (/ (sqrt (+ (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 2) (pow (+ (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 1.5) 2)) (pow (+ 0.8 (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1))))))) 2))) 1)))))) 2) 2)) (pow (- (* (+ 0.8 (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1))))))) (+ 1 (* 2 (exp (- (/ (sqrt (+ (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 2) (pow (+ (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1)))))) 1.5) 2)) (pow (+ 0.8 (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) (- 1 (* 0.5 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8) 2))) 1))))))) 2))) 1)))))) -0.2) 2))) 0.9)))))) 5.612245))) (exp (* -16 (- (/ (- (log (+ (exp (* -5.612245 (- (- (sqrt (+ (+ (pow (- (* (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5)))))) -0.5) 2) (pow (* (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5)))))) 2)) (pow (- (* (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5)))))) -0.5) 2))) 0.5)))) (exp (* -5.612245 (- (sqrt (+ (pow (+ 0.3 (* (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5))))))) 2) (+ (pow (* (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5)))))) 2) (pow (- (* (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) (+ 1 (* 1.2 (exp (- (/ (sqrt (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.48241276) (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.7513133)) 0.5854049) 2) (+ (pow (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.33768892) 0.55581105) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.5259193)) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.625) 1.09375) 2)))) 0.5)))))) -0.5) 2)))) 0.2)))))) 5.612245))))))) 16)))) (exp (* -11 (- (sqrt (+ (pow (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.4) 2) (+ (pow (/ (- (- (+ (* (/ y 1.5) 2.0808594) (* (/ z 1) 2.3650744)) (* (/ x 1.5) 1.089751)) 0.92795134) 0.7) 2) (pow (/ (- 0.0625 (- (* (/ z 1) 1.7215275) (+ (* (/ y 1.5) 2.5876334) (* (/ x 1.5) 1.2048262)))) 1.5) 2)))) 0.2)))))) 11))) (exp (* -11 (- (sqrt (+ (pow (- (* (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) -1) 2) (+ (pow (* (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) 2) (pow (* (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) 2)))) 0.3)))))) 11))) (exp (* -30.555555 (fmax (fmax (fmax (- -1.5 (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) (- (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 1.5)) (fmax (- 0.5 (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3)) (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 3))) (fmax (- (- (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) (/ 0.1 (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3))) -0.7)) (- (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) (/ 0.1 (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3))) -0.7)))))))) 30.555555)))) (exp (* -11 (fmin (fmin (fmax (- (sqrt (+ (pow (/ (* (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1)))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1) 2) (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1.5) 2)))) 0.5)))))) 0.8) 2) (+ (pow (- (* (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1.5) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1)))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1) 2) (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1.5) 2)))) 0.5)))))) 1.5) 2) (pow (* (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1)))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1)))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1) 2) (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (- (+ (* (/ z 1) 0.5766893) (* (/ y 1.5) 2.1271143)) (* (/ x 1.5) 0.10320534)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.7495963)) 1.115259) 0.6) 2) (+ (pow (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) 2) (pow (/ (- (+ 0.9244498 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.6) 2)))) 1))))) (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))))) -1.5) 2)))) 0.5)))))) 2)))) 0.7) (fmax (fmax (- (+ (- (+ (* (/ z 1) 0.96114874) (* (/ y 1.5) 3.5451903)) (* (/ x 1.5) 0.17200887)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.2493271)) 2.8587651) (fmax (- 0.858765 (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.2493271) (- (+ (* (/ z 1) 0.96114874) (* (/ y 1.5) 3.5451903)) (* (/ x 1.5) 0.17200887)))) (- (+ 0.54074955 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.6334443)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.33111554)))) (fmax (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (* (/ z 1) 0.35865277) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172)))) 1.4760667) (fmax (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0522937) (- (+ (* (/ z 1) 0.35865277) 0.5239333) (+ (* (/ y 1.5) 4.247789) (* (/ x 1.5) 0.6231172))))) (- (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.33111554) (+ 2.5407495 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.6334443))))))) (fmax (- (sqrt (+ (pow (/ (* (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1.5) 2)))) 0.5)))))) 0.8) 2) (+ (pow (- (* (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1.5) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1.5) 2)))) 0.5)))))) 1.5) 2) (pow (* (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.38941833) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.921061)) 0.75479674) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.921061) 1.2962569) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.38941833)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889))) -1.5) 2)))) 0.5)))))) 2)))) 0.7) (fmax (fmax (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0234011) (+ 0.16133696 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.43268704)))) (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.43268704) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0234011)) 1.8386631)) (fmax (fmax (- (* (+ (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 1.1111112)) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.8888889)) (fmax (- (+ 0.44028544 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.0234011)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.43268704)) (- (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.43268704) (+ 2.4402854 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.0234011)))))))) (fmax (- (sqrt (+ (pow (/ (* (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1.5) 2)))) 0.5)))))) 0.8) 2) (+ (pow (- (* (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1.5) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1.5) 2)))) 0.5)))))) 1.5) 2) (pow (* (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1) (+ 1 (* 4 (exp (- (/ (sqrt (+ (pow (- (+ (* (- (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1)))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (pow (- (+ (* (cos (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9)) (* (sin (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.19866933) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.9800666)) 0.8012642) 0.9) 2) (+ (pow (/ (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.9800666) 1.0807292) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.19866933)) 0.9) 2) (pow (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221) 2)))) 1))))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221))) -1.5) 2)))) 0.5)))))) 2)))) 0.7) (fmax (fmax (- (+ (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0889629) (+ 0.1097064 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.2207437)))) (- (+ (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 0.2207437) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 1.0889629)) 1.8902936)) (fmax (fmax (- (+ 1.7777778 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112))) (- (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1.1111112) 0.22222221)) (fmax (- (+ 0.20081031 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.0889629)) (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.2207437)) (- (* (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.2207437) (+ 2.2008104 (* (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 1.0889629))))))))))))) 11) (fmin (- (sqrt (+ (pow (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.4) 2) (+ (pow (/ (- (- (+ (* (/ y 1.5) 2.0808594) (* (/ z 1) 2.3650744)) (* (/ x 1.5) 1.089751)) 0.92795134) 0.7) 2) (pow (/ (- 0.0625 (- (* (/ z 1) 1.7215275) (+ (* (/ y 1.5) 2.5876334) (* (/ x 1.5) 1.2048262)))) 1.5) 2)))) 0.25) (- (/ (- (log (+ (exp (* -30.555555 (- (- (sqrt (+ (pow (- (* (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) -1) 2) (+ (pow (* (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) 2) (pow (* (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) (- 1 (* 2 (exp (- (/ (sqrt (+ (pow (- (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 1) 2) (+ (pow (* (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) 2) (pow (- (* (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) (- 1 (exp (- (/ (sqrt (+ (+ (pow (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3) 2) (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2) 2)) (pow (- (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 1) 2))) 0.5))))) -1) 2)))) 0.5)))))) 2)))) 0.4)))) (exp (* -30.555555 (- (sqrt (+ (+ (pow (- (* (- (* (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.2) (- 1 (exp (- (/ (sqrt (+ (+ (pow (- (* (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.2) 2) (pow (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 2)) (pow (- (* (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.35) 2))) 0.15))))) -0.2) 2) (pow (* (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) (- 1 (exp (- (/ (sqrt (+ (+ (pow (- (* (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.2) 2) (pow (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 2)) (pow (- (* (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.35) 2))) 0.15))))) 2)) (pow (- (* (- (* (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.35) (- 1 (exp (- (/ (sqrt (+ (+ (pow (- (* (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.2) 2) (pow (* (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 2)) (pow (- (* (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) (- 1 (* 1.5 (exp (- (/ (sqrt (+ (pow (/ (- (fabs (/ (+ (* 0.87758255 (+ (* 0.9950042 (/ x 1.5)) (* -0.099833414 (/ y 1.5)))) (* 0.47942555 (/ z 1))) 0.3)) 0.25) 0.9) 2) (+ (pow (- (/ (- (* (/ z 1) 0.8753842) (+ (* (/ x 1.5) 0.4828974) (* (/ y 1.5) 0.022641014))) 0.3) 2.19) 2) (pow (+ 0.30833334 (* (/ (+ (+ (* (/ z 1) 0.06207773) (* (/ x 1.5) 0.06583953)) (* (/ y 1.5) 0.99589735)) 0.3) 0.8333333)) 2)))) 0.15)))))) 0.35) 2))) 0.15))))) -0.1) 2))) 0.14)))))) 30.555555))))) ================================================ FILE: bench/graphics/fidget/colonnade.fpcore ================================================ (FPCore (x y z) :name "Model of a colonnade with a balcony and outside staircase" :precision binary64 (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (fmax (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 10) 3.5) (- 0.5 (* y 10))) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10))) (- (sqrt (+ (pow (- (* y 10) 2) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- (* y 10) 7) (- 4 (* y 10))) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (- (* y 10) 5.5) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- 7.5 (* y 10)) (- (* y 10) 10.5)) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (- (* y 10) 9) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- (+ 3 (* y 10))) (* y 10)) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (+ 1.5 (* y 10)) 2) (pow (+ 1 (* z 10)) 2))) 1.5))) (+ 2.5 (* x 10))) (- (+ 3 (* x 10)))) (- (* z 10) 2.5)) (- (* y 10) 9)) (- (+ 3.5 (* y 10)))) (- (+ 3.5 (* z 10)))) (fmax (fmax (fmax (fmax (fmax (fmax (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 10) 3.5) (- 0.5 (* y 10))) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10))) (- (sqrt (+ (pow (- (* y 10) 2) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- (* y 10) 7) (- 4 (* y 10))) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (- (* y 10) 5.5) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- 7.5 (* y 10)) (- (* y 10) 10.5)) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (- (* y 10) 9) 2) (pow (+ 1 (* z 10)) 2))) 1.5)) (fmax (fmax (fmax (- (+ 3 (* y 10))) (* y 10)) (- (+ 3.5 (* z 10)))) (+ 1 (* z 10)))) (- (sqrt (+ (pow (+ 1.5 (* y 10)) 2) (pow (+ 1 (* z 10)) 2))) 1.5))) (- (* x 10) 5.7)) (- 5.2 (* x 10))) (- (* z 10) 2.5)) (- (* y 10) 9)) (- (+ 3.5 (* y 10)))) (- (+ 3.5 (* z 10))))) (fmax (fmax (fmax (fmax (fmax (fmax (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (* z 10) 16.5) (- 3.5 (* z 10))) (+ 4.1 (* y 10))) (- (+ (* y 10) 13.5))) (- (* x 10) 9)) (- 5 (* x 10))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 16.5) (- (* x 10) 9)) (- 5 (* x 10))) (- 0.5 (* z 10))) (+ 7.5 (* y 10))) (- (+ 8.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* x 10) 9) (- 3.1 (* z 10))) (- (* y 10) 10.5)) (- 6.5 (* y 10))) (- 3 (* x 10))) (- (* z 10) 6.5))) (fmax (fmax (fmax (fmax (fmax (fmax (- (* z 10) 16.5) (+ 4.1 (* y 10))) (- 5 (* x 10))) (- 1.5 (* z 10))) (- (+ 6.1 (* y 10)))) (- (* x 10) 5.8)) (- (* x 8) (+ 2.5 (* z 10))))) (fmax (fmax (fmax (fmax (fmax (- 5 (* x 10)) (- 3.1 (* z 10))) (- (* x 10) 5.8)) (- (* z 10) 6)) (- (* y 10) 6.2)) (- (+ 2.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- 5 (* x 10)) (- (* z 10) 5.8)) (- (* y 10) 6)) (- (+ 2.3 (* y 10)))) (- (* x 10) 6)) (- 3.3 (* z 10)))) (fmax (fmax (fmax (fmax (fmax (fmax (- (* z 10) 16.5) (+ 4.1 (* y 10))) (- 1.5 (* z 10))) (- (+ 6.1 (* y 10)))) (- 6.7 (* x 10))) (- (* x 10) 7.5)) (- 7.5 (+ (* x 8) (* z 10))))) (fmax (fmax (fmax (fmax (fmax (- 3.1 (* z 10)) (- (* z 10) 6)) (- (* y 10) 6.2)) (- (+ 2.5 (* y 10)))) (- 6.7 (* x 10))) (- (* x 10) 7.5))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 5.8) (- (* y 10) 6)) (- (+ 2.3 (* y 10)))) (- (* x 10) 7.5)) (- 6.5 (* x 10))) (- 3.3 (* z 10))))) (- 5.5 (* x 10))) (- (* y 10) 9)) (- (+ 8.5 (* y 10)))) (- (* x 10) 7)) (- (* z 10) 6.5)) (- (+ 3.5 (* z 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 3.5) (+ 5.4 (* y 10))) (- (+ 6.5 (* y 10)))) (- (* x 10) 6.8)) (- 5.7 (* x 10))) (- 3.3 (* z 10)))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 6.7) (- (* y 10) 6.5)) (- (+ 4.3 (* y 10)))) (- (* x 10) 7.2)) (- 5.3 (* x 10))) (- 6.5 (* z 10)))) (fmax (fmax (fmax (fmax (fmax (+ (+ (* z 1.7238) 5.43983) (* y 9.8503)) (- (* z 1.7238) (+ 7.95658 (* y 9.8503)))) (- (+ (* z 1.84289) (* x 9.82872)) 7.48826)) (- (+ (* z 1.84289) 4.79765) (* x 9.82872))) (- (* z 10) 3.9)) (- 3.3 (* z 10)))) (fmax (fmax (- (* x 10) 6.7) (- 5.8 (* x 10))) (- (sqrt (+ (pow (+ 4.1 (* y 10)) 2) (pow (- (* z 10) 3.3) 2))) 1.5))) (fmax (fmax (fmax (fmax (- (* x 10) 6.8) (- 5.7 (* x 10))) (- (sqrt (+ (pow (+ 4.1 (* y 10)) 2) (pow (- (* z 10) 3.3) 2))) 1.5)) (- 1.3 (sqrt (+ (pow (+ 4.1 (* y 10)) 2) (pow (- (* z 10) 3.3) 2))))) (- 3.3 (* z 10)))) (- (sqrt (+ (+ (pow (- (* z 10) 5.6) 2) (pow (- (* x 10) 4.85) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.1)) (fmax (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 2.8 (* z 10)) (- 5.4 (* y 10))) (- (* x 10) 9)) (- (* y 10) 9)) (- (* z 10) 3.1)) (- (+ 9 (* x 10)))) (fmax (fmax (fmax (- (* z 30) (+ 5.4 (* y 10))) (- (fmax (- (* z 30) (+ 3 (* y 10))) (- (fmin (- 9 (* x 10)) (- (* x 10) 5.5)))))) (- (fmin (+ 0.0999999 (* z 10)) (- 3.1 (* z 10))))) (- (fmin (- 9 (* x 10)) (- (* x 10) 5.5))))) (fmax (fmax (- (* z 30) (+ 9.3 (* y 10))) (- (fmin (- 9 (* x 10)) (- (* x 10) 5.5)))) (- (fmin (fmin (fmax (- (fmin (- 9 (* x 10)) (- (* x 10) 5.5))) (- (* z 30) (+ 6.9 (* y 10)))) (- 0.2 (* z 10))) (+ 8.5 (* y 10)))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 0.2) (- (+ 0.0999999 (* z 10)))) (+ 3.2 (* y 10))) (- (+ 7.2 (* y 10)))) (- 7 (* x 10))) (- (* x 10) 9))) (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (- 0.2 (* z 10)) (+ 3 (* y 10))) (fmax (- 0.371 (* z 10)) (+ 2.5 (* y 10)))) (fmax (+ 2 (* y 10)) (- 0.542 (* z 10)))) (fmax (- 0.713 (* z 10)) (+ 1.5 (* y 10)))) (fmax (+ 1 (* y 10)) (- 0.884 (* z 10)))) (fmax (+ 0.5 (* y 10)) (- 1.055 (* z 10)))) (fmax (- 1.226 (* z 10)) (* y 10))) (fmax (- (* y 10) 0.5) (- 1.397 (* z 10)))) (fmax (- (* y 10) 1) (- 1.568 (* z 10)))) (fmax (- (* y 10) 1.5) (- 1.739 (* z 10)))) (fmax (- 1.91 (* z 10)) (- (* y 10) 2))) (fmax (- (* y 10) 2.5) (- 2.081 (* z 10)))) (fmax (- (* y 10) 3) (- 2.252 (* z 10)))) (fmax (- 2.423 (* z 10)) (- (* y 10) 3.5))) (fmax (- (* y 10) 4) (- 2.594 (* z 10)))) (fmax (- (* y 10) 4.5) (- 2.765 (* z 10)))) (fmax (- (* y 10) 5) (- 2.936 (* z 10)))) (fmax (- 3.107 (* z 10)) (- (* y 10) 5.5))) (fmax (- 3.278 (* z 10)) (- (* y 10) 6))) (fmax (- 3.449 (* z 10)) (- (* y 10) 6.5))) (fmax (+ 9.2 (* y 10)) (- (+ 0.65 (* z 10))))) (fmax (+ 8.7 (* y 10)) (- (+ 0.479 (* z 10))))) (fmax (+ 8.2 (* y 10)) (- (+ 0.308 (* z 10))))) (fmax (+ 7.7 (* y 10)) (- (+ 0.137 (* z 10))))) (fmax (+ 7.2 (* y 10)) (- 0.0339999 (* z 10))))))) (fmax (fmax (- (sqrt (+ (pow (+ 3.15 (* y 10)) 2) (pow (+ 2.75 (* x 10)) 2))) 0.1) (- (* z 10) 7.4)) (- 6.5 (* z 10)))) (- (sqrt (+ (+ (pow (- (* z 10) 7.4) 2) (pow (+ 3.15 (* y 10)) 2)) (pow (+ 2.75 (* x 10)) 2))) 0.1)) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.8) (- 2.5 (* z 10))) (- (+ 7.1 (* x 10)))) (+ 2.6 (* y 10))) (- (+ 3.7 (* y 10)))) (+ 6 (* x 10)))) (- (sqrt (+ (+ (pow (- (* z 5) 2.2) 2) (pow (+ 6.55 (* x 10)) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.5)) (fmax (fmax (- (* z 10) 5.6) (- 4.8 (* z 10))) (- (sqrt (+ (pow (+ 6.55 (* x 10)) 2) (pow (+ 3.15 (* y 10)) 2))) 0.1))) (- (sqrt (+ (+ (pow (+ 6.55 (* x 10)) 2) (pow (- (* z 10) 5.6) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.1)) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.8) (- 2.5 (* z 10))) (- (* x 10) 1.6)) (- 0.5 (* x 10))) (+ 2.6 (* y 10))) (- (+ 3.7 (* y 10))))) (- (sqrt (+ (+ (pow (- (* z 5) 2.2) 2) (pow (- (* x 10) 1.05) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.5)) (fmax (fmax (- (* z 10) 5.6) (- 4.8 (* z 10))) (- (sqrt (+ (pow (- (* x 10) 1.05) 2) (pow (+ 3.15 (* y 10)) 2))) 0.1))) (- (sqrt (+ (+ (pow (- (* z 10) 5.6) 2) (pow (- (* x 10) 1.05) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.1)) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.8) (- 2.5 (* z 10))) (- (* x 10) 5.4)) (- 4.3 (* x 10))) (+ 2.6 (* y 10))) (- (+ 3.7 (* y 10))))) (- (sqrt (+ (+ (pow (- (* z 5) 2.2) 2) (pow (- (* x 10) 4.85) 2)) (pow (+ 3.15 (* y 10)) 2))) 0.5)) (fmax (fmax (- (* z 10) 5.6) (- 4.8 (* z 10))) (- (sqrt (+ (pow (- (* x 10) 4.85) 2) (pow (+ 3.15 (* y 10)) 2))) 0.1))) (fmax (fmax (fmax (fmax (fmax (fmax (fmax (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 10) 5.5) (- (+ 9 (* x 10)))) (- (* z 10) 4.2)) (- 3.3 (* z 10))) (+ 3.4 (* y 10))) (- (+ 3.6 (* y 10)))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 8.1 (* x 10))) (- (+ 8.9 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 7.15 (* x 10))) (- (+ 7.95 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 5.2 (* x 10))) (- (+ 6 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 4.25 (* x 10))) (- (+ 5.05 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 3.3 (* x 10))) (- (+ 4.1 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 1.4 (* x 10))) (- (+ 2.2 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (+ 0.45 (* x 10))) (- (+ 1.25 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (- (* x 10) 0.5)) (- (+ 0.3 (* x 10)))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (- (* x 10) 2.4)) (- 1.6 (* x 10))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (- (* x 10) 3.35)) (- 2.55 (* x 10))) (- (+ 3.5 (* y 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 4.1) (- 3.4 (* z 10))) (+ 2.5 (* y 10))) (- (* x 10) 4.3)) (- 3.5 (* x 10))) (- (+ 3.5 (* y 10)))))) (+ 3 (* y 10))) (- (+ 3.5 (* y 10)))) (- (* z 10) 4.4)) (- (+ 3.5 (* z 10)))) (- (* x 10) 6)) (- (+ 9 (* x 10)))) (- (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 3.5 (* z 10))) (+ 1 (* z 10))) (+ 7.5 (* x 10))) (- (+ (* x 10) 10.5))) (- (sqrt (+ (pow (+ 1 (* z 10)) 2) (pow (+ 9 (* x 10)) 2))) 1.5)) (fmax (fmax (fmax (- (+ 3.5 (* z 10))) (+ 1 (* z 10))) (+ 4 (* x 10))) (- (+ 7 (* x 10))))) (- (sqrt (+ (pow (+ 1 (* z 10)) 2) (pow (+ 5.5 (* x 10)) 2))) 1.5)) (fmax (fmax (fmax (- (+ 3.5 (* z 10))) (+ 1 (* z 10))) (- (* x 10) 1.5)) (- (+ 1.5 (* x 10))))) (- (sqrt (+ (pow (+ 1 (* z 10)) 2) (pow (* x 10) 2))) 1.5)) (fmax (fmax (fmax (- (+ 3.5 (* z 10))) (+ 1 (* z 10))) (- (* x 10) 5)) (- 2 (* x 10)))) (- (sqrt (+ (pow (+ 1 (* z 10)) 2) (pow (- (* x 10) 3.5) 2))) 1.5))))) (fmax (fmax (fmax (fmax (fmax (- (+ 4 (* z 10))) (- (* y 10) 9)) (- (+ 8.5 (* y 10)))) (- (* x 10) 7)) (+ 3.5 (* z 10))) (- (+ 9 (* x 10))))) (fmax (fmax (fmax (fmax (fmax (- (* y 10) 9) (- (* x 10) 7)) (- (* z 10) 2.3)) (- 2 (* z 10))) (- (+ 3.9 (* y 10)))) (- (+ 9 (* x 10))))) (fmax (fmax (fmax (fmax (fmax (- (* y 10) 9) (- (* x 10) 7)) (- (* z 10) 3.1)) (- 2.3 (* z 10))) (- (+ 3.8 (* y 10)))) (- (+ 9 (* x 10))))) (fmax (fmax (fmax (fmax (fmax (- (* x 10) 7) (- (+ 3.9 (* y 10)))) (- (* z 10) 3.2)) (- 2.9 (* z 10))) (+ 3.5 (* y 10))) (- (+ 9 (* x 10))))) (fmax (fmax (fmax (fmax (fmax (- (* z 10) 6.5) (+ 2.6 (* y 10))) (- (+ 3.7 (* y 10)))) (+ 2.2 (* x 10))) (- (+ 3.3 (* x 10)))) (- (+ 3.5 (* z 10))))) (- (sqrt (+ (+ (pow (- (* z 5) 3.05) 2) (pow (+ 3.15 (* y 10)) 2)) (pow (+ 2.75 (* x 10)) 2))) 0.5))) ================================================ FILE: bench/graphics/fidget/gyroid-sphere.fpcore ================================================ (FPCore (x y z) :name "Gyroid sphere" :precision binary64 (let* ([scale 30] [gyroid (+ (+ (* (sin (* x scale)) (cos (* y scale))) (* (sin (* y scale)) (cos (* z scale)))) (* (sin (* z scale)) (cos (* x scale))))] [fill (- (fabs gyroid) 0.2)] [sphere (- (sqrt (+ (+ (pow (* x scale) 2) (pow (* y scale) 2)) (pow (* z scale) 2))) 25)]) (fmax sphere fill))) ================================================ FILE: bench/graphics/fidget/hi.fpcore ================================================ (FPCore (x y) :name "The letters hi in the upper-right quadrant" :precision binary64 (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- y 0.55) (- y)) (- x 0.825)) (- 0.725 x)) (- (sqrt (+ (pow (- y 0.7) 2) (pow (- x 0.775) 2))) 0.075)) (fmax (fmax (fmax (- y) (- y 0.275)) (- x 0.55)) (- 0.45 x))) (fmax (fmax (fmax (- y) (- y 1)) (- x 0.1)) (- x))) (fmax (fmax (fmax (fmax (fmax (- y 0.55) (- x 0.55)) (- x)) (- 0.275 y)) (- 0.175 (sqrt (+ (pow (- y 0.275) 2) (pow (- x 0.275) 2))))) (- (sqrt (+ (pow (- y 0.275) 2) (pow (- x 0.275) 2))) 0.275)))) ================================================ FILE: bench/graphics/fidget/prospero.fpcore ================================================ (FPCore (x y) :name "Text of a monologue from The Tempest" :precision binary64 (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (+ 2.95 (* x 8.13008)) (- (+ 3.675 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (+ 2.95 (* x 8.13008))) 2) (pow (+ 6.025 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (- (+ 2.95 (* x 8.13008))) 2) (pow (+ 6.025 (* y 8.13008)) 2))) 0.275)) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmax (fmax (fmax (+ 4.025 (* x 8.13008)) (- (+ 4.125 (* x 8.13008)))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008))))) (fmin (fmax (fmax (fmax (+ 4.275 (* x 8.13008)) (- (+ 4.375 (* x 8.13008)))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmin (fmax (fmax (fmax (+ 5.5 (* y 8.13008)) (- (+ 5.75 (* y 8.13008)))) (+ 4.5 (* x 8.13008))) (- (+ 4.6 (* x 8.13008)))) (fmax (fmax (fmax (- (+ 5.4 (* y 8.13008))) (+ 4.7 (* x 8.13008))) (- (+ 5.2 (* x 8.13008)))) (+ 5.3 (* y 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (+ 4.7 (* x 8.13008)) (- (+ 5.2 (* x 8.13008)))) (+ 6.2 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmax (fmax (fmax (+ 4.9 (* x 8.13008)) (- (+ 5 (* x 8.13008)))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008))))) (fmin (fmax (fmax (fmax (+ 4.2 (* y 8.13008)) (- (+ 5.2 (* y 8.13008)))) (- 5.7205 (* x 8.13008))) (- (* x 8.13008) 5.8205)) (fmin (fmax (fmax (fmax (+ 4.65 (* y 8.13008)) (- (+ 4.75 (* y 8.13008)))) (- 5.54551 (* x 8.13008))) (- (* x 8.13008) 5.7205)) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (- 5.54551 (* x 8.13008))) (- (* x 8.13008) 5.7205)) (+ 5.1 (* y 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (- (+ 1.53565 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ 0.90565 (* y 2.03252)))) (- (+ 0.575 (* y 0.813008)))) (fmax (fmax (- (+ (+ (* y 2.03252) 2.24435) (* x 4.47154))) (+ (+ 2.81935 (* y 2.84553)) (* x 4.47154))) (- (+ 0.63 (* y 0.813008))))) (fmin (fmax (fmax (+ (+ (* y 2.03252) 2.24435) (* x 4.47154)) (- (+ (+ 2.81935 (* y 2.84553)) (* x 4.47154)))) (+ 0.63 (* y 0.813008))) (fmin (fmax (fmax (- (+ (+ 2.81935 (* y 2.84553)) (* x 4.47154))) (+ (+ (* y 2.03252) 2.18935) (* x 4.47154))) (+ 0.575 (* y 0.813008))) (fmax (fmax (+ (+ 2.81935 (* y 2.84553)) (* x 4.47154)) (- (+ (+ (* y 2.03252) 2.18935) (* x 4.47154)))) (- (+ 0.575 (* y 0.813008))))))) (fmin (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 1.842 (* x 8.13008)) 2) (pow (+ 6.025 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (+ 1.842 (* x 8.13008)) 2) (pow (+ 6.025 (* y 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (+ 2.475 (* x 8.13008)) (- (+ 2.575 (* x 8.13008)))) (+ 5.75 (* y 8.13008))) (- (+ 6.3 (* y 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.12857 (* x 11.6144)) (- (+ 3.67857 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 6.3 (* y 8.13008)) 2) (pow (+ 3.91072 (* x 14.518)) 2))))) (- (sqrt (+ (pow (+ 6.3 (* y 8.13008)) 2) (pow (+ 3.12857 (* x 11.6144)) 2))) 0.55)) (+ 5.75 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 2.775 (* x 8.13008))) (+ 2.675 (* x 8.13008))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmax (fmax (fmax (+ 2.775 (* x 8.13008)) (- (+ 2.95 (* x 8.13008)))) (+ 5.75 (* y 8.13008))) (- (+ 5.85 (* y 8.13008))))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 2.775 (* x 8.13008)) (- (+ 2.95 (* x 8.13008)))) (+ 6.2 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmax (- (fmin (- (sqrt (+ (pow (- 1.33245 (* x 3.61337)) 2) (pow (- (+ 4.815 (* y 8.13008))) 2))) 0.0625) (fmax (fmax (fmax (- (+ 4.975 (* y 8.13008))) (+ 4.8125 (* y 8.13008))) (- 2.00117 (* x 5.42005))) (- (* x 5.42005) 2.16367)))) (- (sqrt (+ (pow (- (+ 4.8125 (* y 8.13008))) 2) (pow (- 2.00117 (* x 5.42005)) 2))) 0.1625))) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (+ 5.0375 (* y 8.13008))) (- (* x 5.42005) 2.00117)) (- 1.83867 (* x 5.42005))) (+ 4.875 (* y 8.13008))) (- (sqrt (+ (pow (+ 5.035 (* y 8.13008)) 2) (pow (- (* x 3.61337) 1.33578) 2))) 0.0625))) (- (sqrt (+ (pow (+ 5.0375 (* y 8.13008)) 2) (pow (- (* x 5.42005) 2.00117) 2))) 0.1625)) (fmin (fmax (fmax (fmax (+ 4.2 (* y 8.13008)) (- (+ 4.95 (* y 8.13008)))) (- (* x 8.13008) 1.808)) (- 1.708 (* x 8.13008))) (fmax (fmax (fmax (+ 4.55 (* y 8.13008)) (- (+ 4.65 (* y 8.13008)))) (- (* x 8.13008) 1.958)) (- 1.558 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.95 (* y 8.13008))) (- (* x 8.13008) 1.958)) (- 1.558 (* x 8.13008))) (- 0.15 (sqrt (+ (pow (+ 4.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.958) 2))))) (- (sqrt (+ (pow (+ 4.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.958) 2))) 0.25)) (fmax (fmax (fmax (fmax (fmax (+ 4.2 (* y 8.13008)) (- (+ 5.2 (* y 8.13008)))) (- 4.8205 (* x 8.13008))) (- (* x 8.13008) 5.5455)) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- 5.54551 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- 5.54551 (* x 8.13008)) 2))) 0.275))) (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.875 (* y 8.13008))) (- (* x 8.13008) 5.1955)) (- 5.0955 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.65 (* y 8.13008))) (- (* x 8.13008) 4.7455)) (- 4.6455 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (+ 4.65 (* y 8.13008)) (- (* x 8.13008) 5.1955)) (- 4.6455 (* x 8.13008))) (- (+ 4.875 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.9205) 2))))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.9205) 2))) 0.275)))))) (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.65 (* y 8.13008))) (- (* x 8.13008) 4.5455)) (- 4.4455 (* x 8.13008))) (fmax (fmax (fmax (+ 4.65 (* y 8.13008)) (- (+ 4.925 (* y 8.13008)))) (- (* x 8.13008) 4.0955)) (- 3.9955 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.925 (* y 8.13008))) (- (* x 8.13008) 4.5455)) (- 3.9955 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.2705) 2))))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.2705) 2))) 0.275)) (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.6205) 2)))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.6205) 2))) 0.275)) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 0.142001 (* x 8.13008))) (- (+ 0.242001 (* x 8.13008)))) (+ 4.85 (* y 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 0.392001 (* x 8.13008))) (- (+ 0.492001 (* x 8.13008)))) (+ 4.675 (* y 8.13008))) (fmin (fmax (fmax (fmax (fmax (+ 4.55 (* y 8.13008)) (- (+ 0.492001 (* x 8.13008)))) (- (* x 8.13008) 0.157999)) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 0.0670004 (* x 8.13008)) 2) (pow (+ 4.85 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (+ 0.0670004 (* x 8.13008)) 2) (pow (+ 4.85 (* y 8.13008)) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 0.317 (* x 8.13008)) 2) (pow (+ 4.85 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (+ 0.317 (* x 8.13008)) 2) (pow (+ 4.85 (* y 8.13008)) 2))) 0.175)))) (- (+ 4.85 (* y 8.13008)))) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.65 (* y 8.13008))) (+ 0.592 (* x 8.13008))) (- (+ 0.692001 (* x 8.13008)))))) (fmin (fmax (fmax (fmax (+ 4.65 (* y 8.13008)) (- (+ 4.925 (* y 8.13008)))) (+ 1.042 (* x 8.13008))) (- (+ 1.142 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.925 (* y 8.13008))) (+ 0.592 (* x 8.13008))) (- (+ 1.142 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 0.867001 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 0.867001 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (+ 4.2 (* y 8.13008)) (- (+ 5.2 (* y 8.13008)))) (+ 1.267 (* x 8.13008))) (- (+ 1.367 (* x 8.13008)))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 4.65 (* y 8.13008)) (- (+ 5.575 (* y 8.13008)))) (+ 1.942 (* x 8.13008))) (- (+ 2.042 (* x 8.13008)))) (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.183) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 4.885 (* y 8.13008)) (- (+ 4.975 (* y 8.13008)))) (- (* x 8.13008) 1.458)) (- 0.958001 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.183) 2))) 0.275) (- (fmin (fmax (fmax (+ 3.20125 (* y 5.28455)) (- (* x 2.23577) (+ 1.1947 (* y 1.21951)))) (- (+ (+ 2.1853 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (+ (+ 2.1853 (* x 2.23577)) (* y 4.06504)) (- (+ 1.1947 (* y 1.21951)) (* x 2.23577))) (- (+ 3.20125 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.183) 2)))))))) (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.85 (* y 8.13008))) (- (* x 8.13008) 0.808001)) (- 0.708 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.85 (* y 8.13008))) (- (* x 8.13008) 0.558001)) (- 0.458 (* x 8.13008))) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.675 (* y 8.13008))) (- (* x 8.13008) 0.308001)) (- 0.208 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (fmax (+ 4.55 (* y 8.13008)) (- 0.208 (* x 8.13008))) (- (+ 4.85 (* y 8.13008)))) (- (* x 8.13008) 0.858)) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 4.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.633) 2)))) (- (sqrt (+ (pow (+ 4.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.633) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 4.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.383) 2)))) (- (sqrt (+ (pow (+ 4.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.383) 2))) 0.175)))) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.85 (* y 8.13008))) (- (* x 8.13008) 0.108)) (- 0.00799942 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 1.767 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 1.767 (* x 8.13008)) 2))) 0.275)) (fmin (fmax (fmax (+ (+ (* y 2.84553) 4.13) (* x 4.47154)) (- (+ 0.52 (* y 0.813008)))) (- (+ (+ (* y 2.03252) 3.665) (* x 4.47154)))) (fmax (fmax (- (+ (+ (* y 2.84553) 4.13) (* x 4.47154))) (+ 0.52 (* y 0.813008))) (+ (+ (* y 2.03252) 3.665) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (- (+ (+ (* y 2.84553) 4.13) (* x 4.47154))) (+ (+ (* y 2.03252) 3.61) (* x 4.47154))) (+ 0.465 (* y 0.813008))) (fmax (fmax (+ (+ (* y 2.84553) 4.13) (* x 4.47154)) (- (+ (+ (* y 2.03252) 3.61) (* x 4.47154)))) (- (+ 0.465 (* y 0.813008))))) (fmin (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 4.925 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 4.885 (* y 8.13008)) (- (+ 4.975 (* y 8.13008)))) (+ 4.65 (* x 8.13008))) (- (+ 5.15 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 4.925 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (+ 3.20125 (* y 5.28455)) (- (+ 0.485 (* x 2.23577)) (* y 1.21951))) (- (+ (+ (* x 2.23577) 3.865) (* y 4.06504)))) (fmax (fmax (- (+ 3.20125 (* y 5.28455))) (+ (+ (* x 2.23577) 3.865) (* y 4.06504))) (- (* y 1.21951) (+ 0.485 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 4.925 (* x 8.13008)) 2))))))) (fmin (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- 7.531 (* x 8.13008))) (- (* x 8.13008) 7.631)) (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.65 (* y 8.13008)))) (- 7.35601 (* x 8.13008))) (- (* x 8.13008) 7.531))))) (fmin (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (- 7.35601 (* x 8.13008))) (- (* x 8.13008) 7.531)) (+ 4 (* y 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- 6.631 (* x 8.13008))) (- (* x 8.13008) 7.356)) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- 7.35601 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- 7.35601 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.65 (* y 8.13008))) (+ 3.1 (* x 8.13008))) (- (+ 3.2 (* x 8.13008)))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 5.2 (* y 8.13008))) (+ 4.65 (* y 8.13008))) (+ 4.02143 (* x 11.6144))) (- (+ 4.57143 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 5.2 (* y 8.13008)) 2) (pow (+ 5.02679 (* x 14.518)) 2))))) (- (sqrt (+ (pow (+ 5.2 (* y 8.13008)) 2) (pow (+ 4.02143 (* x 11.6144)) 2))) 0.55)) (fmin (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 3.575 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 4.885 (* y 8.13008)) (- (+ 4.975 (* y 8.13008)))) (+ 3.3 (* x 8.13008))) (- (+ 3.8 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 3.575 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (+ 3.20125 (* y 5.28455)) (- (+ 0.11375 (* x 2.23577)) (* y 1.21951))) (- (+ (+ (* x 2.23577) 3.49375) (* y 4.06504)))) (fmax (fmax (- (+ 3.20125 (* y 5.28455))) (+ (+ (* x 2.23577) 3.49375) (* y 4.06504))) (- (* y 1.21951) (+ 0.11375 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (+ 4.925 (* y 8.13008)) 2) (pow (+ 3.575 (* x 8.13008)) 2))))))) (fmax (fmax (- (+ 0.52 (* y 0.813008))) (- (+ 1.01 (* x 4.47154)) (* y 2.03252))) (- (* y 2.84553) (+ 0.545 (* x 4.47154))))))))) (fmin (fmin (fmin (fmin (fmax (fmax (+ 0.52 (* y 0.813008)) (- (+ 0.545 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 1.01 (* x 4.47154)))) (fmax (fmax (- (+ 0.545 (* x 4.47154)) (* y 2.84553)) (+ 0.465 (* y 0.813008))) (- (* y 2.03252) (+ 1.065 (* x 4.47154))))) (fmin (fmax (fmax (- (* y 2.84553) (+ 0.545 (* x 4.47154))) (- (+ 1.065 (* x 4.47154)) (* y 2.03252))) (- (+ 0.465 (* y 0.813008)))) (fmin (- (sqrt (+ (pow (+ 6.225 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.733) 2))) 0.075) (fmax (fmax (fmax (fmax (- (fmin (fmin (fmin (fmax (fmax (+ 2.65 (* y 4.06504)) (- (* x 4.06504) 2.829)) (- (+ 0.0709989 (* (+ x y) 4.06504)))) (fmax (fmax (+ 0.0709989 (* (+ x y) 4.06504)) (- 2.829 (* x 4.06504))) (- (+ 2.65 (* y 4.06504))))) (fmin (fmax (fmax (- (* x 8.13008) (+ (* y 0.813008) 6.188)) (- (+ (+ 0.0706995 (* y 2.60163)) (* x 2.84553)))) (- (+ (* y 3.41463) 5.9037) (* x 5.28455))) (fmax (fmax (- (* x 5.28455) (+ (* y 3.41463) 5.9037)) (+ (+ 0.0706992 (* y 2.60163)) (* x 2.84553))) (- (+ (* y 0.813008) 6.188) (* x 8.13008))))) (fmin (fmin (fmax (fmax (- (+ 1.728 (* y 2.19512))) (- 1.8053 (* x 2.84553))) (- (+ (* y 2.19512) (* x 2.84553)) 0.171801)) (fmax (fmax (+ 1.728 (* y 2.19512)) (- 0.171801 (+ (* y 2.19512) (* x 2.84553)))) (- (* x 2.84553) 1.8053))) (fmin (fmax (fmax (+ 2.12 (* y 3.25203)) (- (* x 4.47154) (+ (* y 3.25203) 5.1769))) (- 2.8369 (* x 4.47154))) (fmax (fmax (- (* x 4.47154) 2.8369) (- (+ (* y 3.25203) 5.1769) (* x 4.47154))) (- (+ 2.12 (* y 3.25203)))))))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (- (* x 8.13008) 5.558)) (- 5.058 (* x 8.13008)))))) (fmin (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.683) 2)))) (- (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.683) 2))) 0.275)) (fmax (- 0.175 (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.033) 2)))) (- (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.033) 2))) 0.275))) (fmin (fmax (fmax (fmax (- (* x 8.13008) 3.233) (- 3.133 (* x 8.13008))) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmin (fmax (fmax (fmax (+ 5.75 (* y 8.13008)) (- (+ 5.85 (* y 8.13008)))) (- (* x 8.13008) 3.408)) (- 3.233 (* x 8.13008))) (fmax (fmax (fmax (- (* x 8.13008) 3.408) (- 3.233 (* x 8.13008))) (+ 6.2 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))))))) (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 4.133) (- 3.408 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.408) 2))))) (- (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.408) 2))) 0.275)) (+ 5.3 (* y 8.13008))) (- (+ 6.3 (* y 8.13008)))) (fmax (fmax (- (+ 0.685 (* y 0.813008))) (- (* x 4.47154) (+ (* y 1.82927) 2.5769))) (- (+ (* y 2.64228) 3.2069) (* x 4.47154)))) (fmin (fmax (fmax (+ 0.685 (* y 0.813008)) (- (* x 4.47154) (+ (* y 2.64228) 3.2069))) (- (+ (* y 1.82927) 2.5769) (* x 4.47154))) (fmin (fmax (fmax (fmax (fmax (- 1.083 (* x 8.13008)) (+ 5.65 (* y 8.13008))) (- (+ 5.95 (* y 8.13008)))) (- (* x 8.13008) 1.733)) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 5.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.508) 2)))) (- (sqrt (+ (pow (+ 5.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.508) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 5.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.258) 2)))) (- (sqrt (+ (pow (+ 5.95 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.258) 2))) 0.175)))) (fmax (fmax (fmax (- (+ 6.3 (* y 8.13008))) (+ 5.975 (* y 8.13008))) (- (* x 8.13008) 0.282999)) (- 0.182999 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (+ 5.75 (* y 8.13008)) (- (+ 6.3 (* y 8.13008)))) (+ 0.167001 (* x 8.13008))) (- (+ 0.267001 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 5.75 (* y 8.13008)) (- (* x 8.13008) 0.282999)) (- (+ 0.267001 (* x 8.13008)))) (- (+ 5.975 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.00799847) 2))))) (- (sqrt (+ (pow (+ 6.025 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.00799847) 2))) 0.275)) (fmax (fmax (- (* x 4.47154) (+ 1.23565 (* y 2.03252))) (- (+ 1.81065 (* y 2.84553)) (* x 4.47154))) (- (+ 0.63 (* y 0.813008)))))) (fmin (fmax (fmax (- (* x 4.47154) (+ 1.81065 (* y 2.84553))) (- (+ 1.23565 (* y 2.03252)) (* x 4.47154))) (+ 0.63 (* y 0.813008))) (fmin (fmax (fmax (- (* x 4.47154) (+ 1.81065 (* y 2.84553))) (- (+ 1.18065 (* y 2.03252)) (* x 4.47154))) (+ 0.575 (* y 0.813008))) (fmax (fmax (- (+ 1.81065 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ 1.18065 (* y 2.03252)))) (- (+ 0.575 (* y 0.813008))))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (- (+ (+ 1.96935 (* y 2.03252)) (* x 4.47154))) (+ (+ 2.54435 (* y 2.84553)) (* x 4.47154))) (- (+ 0.63 (* y 0.813008)))) (fmax (fmax (+ (+ 1.96935 (* y 2.03252)) (* x 4.47154)) (- (+ (+ 2.54435 (* y 2.84553)) (* x 4.47154)))) (+ 0.63 (* y 0.813008)))) (fmin (fmax (fmax (- (+ (+ 2.54435 (* y 2.84553)) (* x 4.47154))) (+ (+ 1.91435 (* y 2.03252)) (* x 4.47154))) (+ 0.575 (* y 0.813008))) (fmin (fmax (fmax (+ (+ 2.54435 (* y 2.84553)) (* x 4.47154)) (- (+ (+ 1.91435 (* y 2.03252)) (* x 4.47154)))) (- (+ 0.575 (* y 0.813008)))) (fmax (fmax (- (* x 4.47154) (+ 0.96065 (* y 2.03252))) (- (+ 1.53565 (* y 2.84553)) (* x 4.47154))) (- (+ 0.63 (* y 0.813008))))))) (fmin (fmin (fmax (fmax (- (* x 4.47154) (+ 1.53565 (* y 2.84553))) (- (+ 0.96065 (* y 2.03252)) (* x 4.47154))) (+ 0.63 (* y 0.813008))) (fmax (fmax (- (* x 4.47154) (+ 1.53565 (* y 2.84553))) (- (+ 0.90565 (* y 2.03252)) (* x 4.47154))) (+ 0.575 (* y 0.813008)))) (fmin (fmax (fmax (- (* x 4.47154) (+ (* y 2.64228) 3.2069)) (+ 0.63 (* y 0.813008))) (- (+ (* y 1.82927) 2.5219) (* x 4.47154))) (fmin (fmax (fmax (- (+ (* y 2.64228) 3.2069) (* x 4.47154)) (- (* x 4.47154) (+ (* y 1.82927) 2.5219))) (- (+ 0.63 (* y 0.813008)))) (fmax (fmax (- (* x 4.47154) (+ (* y 1.82927) 2.5769)) (- (+ 0.63 (* y 0.813008)))) (- (+ (* y 2.64228) 3.1519) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (- (+ (* y 1.82927) 2.5769) (* x 4.47154)) (+ 0.63 (* y 0.813008))) (- (* x 4.47154) (+ (* y 2.64228) 3.1519))) (fmax (fmax (- (+ (* y 1.82927) 2.5219) (* x 4.47154)) (- (* x 4.47154) (+ (* y 2.64228) 3.1519))) (+ 0.575 (* y 0.813008)))) (fmin (fmax (fmax (- (* x 4.47154) (+ (* y 1.82927) 2.5219)) (- (+ (* y 2.64228) 3.1519) (* x 4.47154))) (- (+ 0.575 (* y 0.813008)))) (fmin (fmax (fmax (- (+ 0.63 (* y 0.813008))) (- (+ (+ 0.3131 (* y 1.82927)) (* x 4.47154)))) (+ (+ 0.8881 (* y 2.64228)) (* x 4.47154))) (fmax (fmax (+ 0.63 (* y 0.813008)) (+ (+ 0.3131 (* y 1.82927)) (* x 4.47154))) (- (+ (+ 0.8881 (* y 2.64228)) (* x 4.47154))))))) (fmin (fmin (fmax (fmax (+ 0.575 (* y 0.813008)) (- (+ (+ 0.8881 (* y 2.64228)) (* x 4.47154)))) (+ (+ 0.2581 (* y 1.82927)) (* x 4.47154))) (fmax (fmax (- (+ 0.575 (* y 0.813008))) (+ (+ 0.8881 (* y 2.64228)) (* x 4.47154))) (- (+ (+ 0.2581 (* y 1.82927)) (* x 4.47154))))) (fmin (fmax (fmax (fmax (- (+ 6.3 (* y 8.13008))) (+ 5.95 (* y 8.13008))) (- (* x 8.13008) 1.683)) (- 1.583 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (+ 6.3 (* y 8.13008))) (+ 5.95 (* y 8.13008))) (- (* x 8.13008) 1.433)) (- 1.333 (* x 8.13008))) (fmax (fmax (fmax (- (+ 6.3 (* y 8.13008))) (+ 5.775 (* y 8.13008))) (- (* x 8.13008) 1.183)) (- 1.083 (* x 8.13008)))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 2.65 (* y 8.13008)) (- (* x 8.13008) 1.498)) (- 1.398 (* x 8.13008))) (- (+ 3 (* y 8.13008)))) (fmax (fmax (fmax (+ 2.65 (* y 8.13008)) (- (* x 8.13008) 1.248)) (- 1.148 (* x 8.13008))) (- (+ 3 (* y 8.13008))))) (fmin (fmax (fmax (fmax (+ 2.475 (* y 8.13008)) (- (* x 8.13008) 0.998001)) (- 0.898001 (* x 8.13008))) (- (+ 3 (* y 8.13008)))) (fmin (fmax (fmax (fmax (fmax (- 0.898001 (* x 8.13008)) (- (+ 2.65 (* y 8.13008)))) (- (* x 8.13008) 1.548)) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 2.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.323) 2)))) (- (sqrt (+ (pow (+ 2.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.323) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 2.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.073) 2)))) (- (sqrt (+ (pow (+ 2.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.073) 2))) 0.175)))) (+ 2.35 (* y 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 0.523) 2) (pow (+ 2.725 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (* x 8.13008) 0.523) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (- (* x 8.13008) 0.0979996) (- (+ 0.00200015 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2.725 (* y 8.13008))) (fmax (fmax (fmax (+ 0.352 (* x 8.13008)) (- (+ 0.452 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2 (* y 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 0.0979996) (- (+ 0.452 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 0.177 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 0.177 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (+ 2.45 (* y 8.13008))) (- (+ 2.725 (* y 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 3.4105) 2) (pow (+ 2.725 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (* x 8.13008) 3.4105) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (+ 2.45 (* y 8.13008))) (- (* x 8.13008) 3.6855)) (- 3.1355 (* x 8.13008))) (- (+ 2.675 (* y 8.13008)))) (fmax (fmax (fmax (- (* x 8.13008) 3.0105) (- 2.9105 (* x 8.13008))) (- (+ 3 (* y 8.13008)))) (+ 2.45 (* y 8.13008))))))) (fmin (fmin (fmin (- (sqrt (+ (pow (+ 2.3 (* y 8.13008)) 2) (pow (- (* x 8.13008) 2.9605) 2))) 0.075) (fmax (- (fmin (fmax (fmax (fmax (- (+ 2.775 (* y 8.13008))) (+ 2.6125 (* y 8.13008))) (- 1.22783 (* x 5.42005))) (- (* x 5.42005) 1.39033)) (- (sqrt (+ (pow (- (+ 2.615 (* y 8.13008))) 2) (pow (- 0.81689 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- (+ 2.6125 (* y 8.13008))) 2) (pow (- 1.22783 (* x 5.42005)) 2))) 0.1625))) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (+ 2.8375 (* y 8.13008))) (- (* x 5.42005) 1.22783)) (- 1.06533 (* x 5.42005))) (+ 2.675 (* y 8.13008))) (- (sqrt (+ (pow (+ 2.835 (* y 8.13008)) 2) (pow (- (* x 3.61337) 0.820223) 2))) 0.0625))) (- (sqrt (+ (pow (+ 2.8375 (* y 8.13008)) 2) (pow (- (* x 5.42005) 1.22783) 2))) 0.1625)) (fmin (fmax (fmax (fmax (fmax (fmax (- 0.175 (sqrt (+ (pow (+ 3.207 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (+ 3.207 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (+ 2.932 (* x 8.13008))) (- (+ 3.482 (* x 8.13008)))) (+ 2.45 (* y 8.13008))) (- (+ 2.675 (* y 8.13008)))) (fmax (fmax (fmax (+ 3.607 (* x 8.13008)) (- (+ 3.707 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2.45 (* y 8.13008)))))) (fmin (fmin (- (sqrt (+ (pow (+ 2.3 (* y 8.13008)) 2) (pow (+ 3.657 (* x 8.13008)) 2))) 0.075) (fmin (fmax (fmax (fmax (+ 2.65 (* y 8.13008)) (+ 3.852 (* x 8.13008))) (- (+ 3.952 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (fmax (- 0.175 (sqrt (+ (pow (- (+ 3.35486 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 2.725 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (+ 3.35486 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)))) (fmin (fmax (fmax (fmax (+ 4.662 (* x 8.13008)) (- (+ 4.762 (* x 8.13008)))) (+ 2 (* y 8.13008))) (- (+ 2.75 (* y 8.13008)))) (fmin (fmax (fmax (fmax (+ 4.512 (* x 8.13008)) (- (+ 4.912 (* x 8.13008)))) (+ 2.35 (* y 8.13008))) (- (+ 2.45 (* y 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 4.512 (* x 8.13008)) (- (+ 4.912 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 4.512 (* x 8.13008)) 2) (pow (+ 2.75 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 4.512 (* x 8.13008)) 2) (pow (+ 2.75 (* y 8.13008)) 2))) 0.25)) (- (+ 3 (* y 8.13008)))) (+ 2.75 (* y 8.13008))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 5.27 (* x 8.13008)) (- (+ 5.37 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2.45 (* y 8.13008))) (fmax (fmax (fmax (+ 0.702 (* x 8.13008)) (- (+ 0.802 (* x 8.13008)))) (+ 2 (* y 8.13008))) (- (+ 2.75 (* y 8.13008))))) (fmin (fmax (fmax (fmax (+ 0.552 (* x 8.13008)) (- (+ 0.952 (* x 8.13008)))) (+ 2.35 (* y 8.13008))) (- (+ 2.45 (* y 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 0.552 (* x 8.13008)) (- (+ 0.952 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 0.552 (* x 8.13008)) 2) (pow (+ 2.75 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 0.552 (* x 8.13008)) 2) (pow (+ 2.75 (* y 8.13008)) 2))) 0.25)) (- (+ 3 (* y 8.13008)))) (+ 2.75 (* y 8.13008))) (fmax (fmax (fmax (+ 2.65 (* y 8.13008)) (+ 1.072 (* x 8.13008))) (- (+ 1.172 (* x 8.13008)))) (- (+ 3 (* y 8.13008))))))) (fmin (fmin (fmax (- 0.175 (sqrt (+ (pow (- (+ 0.574857 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 2.725 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (+ 0.574857 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (+ 2.25 (* y 8.13008)) (+ 1.882 (* x 8.13008))) (- (+ 1.982 (* x 8.13008)))) (- (+ 3 (* y 8.13008))))) (fmin (fmax (fmax (fmax (- (+ 2.55 (* y 8.13008))) (+ 1.732 (* x 8.13008))) (- (+ 2.132 (* x 8.13008)))) (+ 2.45 (* y 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 1.732 (* x 8.13008)) (- (+ 2.132 (* x 8.13008)))) (- (+ 2.25 (* y 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 2.25 (* y 8.13008)) 2) (pow (+ 1.732 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 2.25 (* y 8.13008)) 2) (pow (+ 1.732 (* x 8.13008)) 2))) 0.25)) (+ 2 (* y 8.13008))) (fmax (fmax (fmax (+ 2.932 (* x 8.13008)) (- (+ 3.032 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2.675 (* y 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (+ 3.382 (* x 8.13008)) (- (+ 3.482 (* x 8.13008)))) (- (+ 3 (* y 8.13008)))) (+ 2.45 (* y 8.13008))) (fmax (fmax (fmax (+ 1.25 (* y 8.13008)) (- (+ 1.35 (* y 8.13008)))) (- (* x 8.13008) 6.6385)) (- 6.2385 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 6.6385) (- 6.2385 (* x 8.13008))) (- (+ 1.9 (* y 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 1.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 6.6385) 2))))) (- (sqrt (+ (pow (+ 1.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 6.6385) 2))) 0.25)) (+ 1.65 (* y 8.13008))) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (- (* x 8.13008) 6.1135)) (- 6.0135 (* x 8.13008))) (- (sqrt (+ (pow (+ 1.2 (* y 8.13008)) 2) (pow (- (* x 8.13008) 6.0635) 2))) 0.075)))) (fmin (fmin (fmax (fmax (- (+ 0.245 (* y 0.813008))) (- (* x 4.47154) (+ (* y 1.82927) 3.15743))) (- (+ (* y 2.64228) 3.34743) (* x 4.47154))) (fmin (fmax (fmax (+ 0.245 (* y 0.813008)) (- (* x 4.47154) (+ (* y 2.64228) 3.34743))) (- (+ (* y 1.82927) 3.15743) (* x 4.47154))) (fmax (fmax (- (* x 4.47154) (+ (* y 2.64228) 3.34743)) (+ 0.19 (* y 0.813008))) (- (+ (* y 1.82927) 3.10243) (* x 4.47154))))) (fmin (fmax (fmax (- (+ (* y 2.64228) 3.34743) (* x 4.47154)) (- (* x 4.47154) (+ (* y 1.82927) 3.10243))) (- (+ 0.19 (* y 0.813008)))) (fmin (fmax (fmax (- (* x 4.47154) (+ (* y 1.82927) 3.15743)) (- (+ 0.19 (* y 0.813008)))) (- (+ (* y 2.64228) 3.29243) (* x 4.47154))) (fmax (fmax (- (+ (* y 1.82927) 3.15743) (* x 4.47154)) (+ 0.19 (* y 0.813008))) (- (* x 4.47154) (+ (* y 2.64228) 3.29243)))))))) (fmin (fmin (fmin (fmin (fmax (fmax (- (+ (* y 1.82927) 3.10243) (* x 4.47154)) (- (* x 4.47154) (+ (* y 2.64228) 3.29243))) (+ 0.135 (* y 0.813008))) (fmax (fmax (- (* x 4.47154) (+ (* y 1.82927) 3.10243)) (- (+ (* y 2.64228) 3.29243) (* x 4.47154))) (- (+ 0.135 (* y 0.813008))))) (fmin (fmax (fmax (- (+ 0.19 (* y 0.813008))) (- 2.24743 (+ (* y 1.82927) (* x 4.47154)))) (- (+ (* y 2.64228) (* x 4.47154)) 2.11243)) (fmin (fmax (fmax (fmax (fmax (fmax (+ 7.12143 (* x 11.6144)) (- (+ 7.67143 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 3 (* y 8.13008)) 2) (pow (+ 8.90179 (* x 14.518)) 2))))) (- (sqrt (+ (pow (+ 3 (* y 8.13008)) 2) (pow (+ 7.12143 (* x 11.6144)) 2))) 0.55)) (- (+ 3 (* y 8.13008)))) (+ 2.45 (* y 8.13008))) (fmax (- (sqrt (+ (pow (+ 5.745 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 5.47 (* x 8.13008)) (- (+ 5.97 (* x 8.13008)))) (+ 2.685 (* y 8.13008))) (- (+ 2.775 (* y 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 5.745 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (+ 1.0405 (* x 2.23577)) (* y 1.21951)) (- (+ (+ (* x 2.23577) 2.9905) (* y 4.06504)))) (+ 1.77125 (* y 5.28455))) (fmax (fmax (+ (+ (* x 2.23577) 2.9905) (* y 4.06504)) (- (* y 1.21951) (+ 1.0405 (* x 2.23577)))) (- (+ 1.77125 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 5.745 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2)))))))))) (fmin (fmin (fmax (fmax (- (fmin (fmax (fmax (- (* y 2.23577) (+ 1.02163 (* x 2.27642))) (+ 3.35775 (* x 4.5122))) (- (+ (* (+ x y) 2.23577) 2.48875))) (fmax (fmax (+ (* (+ x y) 2.23577) 2.48875) (- (+ 3.35775 (* x 4.5122)))) (- (+ 1.02162 (* x 2.27642)) (* y 2.23577))))) (- 0.175 (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (+ 0.9 (* y 8.13008)) (- (+ 1.65 (* y 8.13008)))) (- (* x 8.13008) 6.4885)) (- 6.3885 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 0.9 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.1805) 2))))) (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.1805) 2))) 0.275)) (- (* x 8.13008) 3.9055)) (- 3.1805 (* x 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (- (fmin (fmax (fmax (- (* x 2.84553) (+ (* y 0.813008) 0.880675)) (- (+ (+ 0.590637 (* x 1.82927)) (* y 4.06504)))) (- (+ 1.27381 (* y 4.87805)) (* x 1.01626))) (fmax (fmax (+ (+ 0.590637 (* x 1.82927)) (* y 4.06504)) (- (* x 1.01626) (+ 1.27381 (* y 4.87805)))) (- (+ (* y 0.813008) 0.880675) (* x 2.84553))))) (+ 1.825 (* y 8.13008))) (- (+ 2.05 (* y 8.13008)))) (- (* x 8.13008) 2.0805)) (- 1.9305 (* x 8.13008))) (- (sqrt (+ (pow (+ 0.608333 (* y 2.71003)) 2) (pow (- (* x 8.13008) 2.0055) 2))) 0.075)) (- (sqrt (+ (pow (+ 1.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 2.0305) 2))) 0.075))))) (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.15 (* y 8.13008))) (- (* x 8.13008) 1.6805)) (- 1.5805 (* x 8.13008))) (fmax (fmax (fmax (+ 1.35 (* y 8.13008)) (- (* x 8.13008) 1.8305)) (- 1.4305 (* x 8.13008))) (- (+ 1.45 (* y 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 0.9 (* y 8.13008)) (- (* x 8.13008) 1.8305)) (- 1.4305 (* x 8.13008))) (- (+ 1.15 (* y 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 1.15 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.8305) 2))))) (- (sqrt (+ (pow (+ 1.15 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.8305) 2))) 0.25)) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.15 (* y 8.13008))) (- (* x 8.13008) 1.1805)) (- 1.0805 (* x 8.13008))) (fmax (fmax (+ 0.19 (* y 0.813008)) (- 2.11243 (+ (* y 2.64228) (* x 4.47154)))) (- (+ (* y 1.82927) (* x 4.47154)) 2.24743))))) (fmin (fmin (fmax (fmax (+ 0.135 (* y 0.813008)) (- 2.11243 (+ (* y 2.64228) (* x 4.47154)))) (- (+ (* y 1.82927) (* x 4.47154)) 2.30243)) (fmin (fmax (fmax (- (+ 0.135 (* y 0.813008))) (- (+ (* y 2.64228) (* x 4.47154)) 2.11243)) (- 2.30243 (+ (* y 1.82927) (* x 4.47154)))) (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (- (* x 8.13008) 4.2805)) (- 4.1805 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (- (* x 11.6144) 6.52214)) (- 5.97215 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (+ 1.9 (* y 8.13008)) 2) (pow (- (* x 14.518) 8.15268) 2))))) (- (sqrt (+ (pow (+ 1.9 (* y 8.13008)) 2) (pow (- (* x 11.6144) 6.52214) 2))) 0.55)) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (- (* x 8.13008) 4.0805)) (- 3.9805 (* x 8.13008))) (fmax (fmax (fmax (+ 1.35 (* y 8.13008)) (- (+ 1.625 (* y 8.13008)))) (- (* x 8.13008) 3.6305)) (- 3.5305 (* x 8.13008))))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (- (* x 8.13008) 4.0805)) (- 3.5305 (* x 8.13008))) (+ 1.625 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.8055) 2))))) (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.8055) 2))) 0.275)) (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 0.9 (* y 8.13008))) (- (* x 8.13008) 3.0055)) (- 2.9055 (* x 8.13008)))) (fmin (fmax (fmax (fmax (+ 1.35 (* y 8.13008)) (- (+ 1.45 (* y 8.13008)))) (- (* x 8.13008) 3.1805)) (- 3.0055 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (- (* x 8.13008) 3.1805)) (- 3.0055 (* x 8.13008))) (+ 1.8 (* y 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) (+ (* y 2.32288) 5.57243)) 2)))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) (+ (* y 2.32288) 5.57243)) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.825 (* y 8.13008))) (- (* x 8.13008) 4.051)) (- 3.951 (* x 8.13008))) (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- (* x 8.13008) 3.601)) (- 3.501 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (* x 8.13008) 4.051)) (- 3.501 (* x 8.13008))) (- (+ 3.825 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.776) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.776) 2))) 0.275)) (fmin (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 3.85 (* y 8.13008)))) (- (* x 8.13008) 3.251)) (- 3.151 (* x 8.13008))) (fmax (fmax (fmax (+ 3.45 (* y 8.13008)) (- (+ 3.55 (* y 8.13008)))) (- (* x 8.13008) 3.401)) (- 3.001 (* x 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.85 (* y 8.13008))) (- (* x 8.13008) 3.401)) (- 3.001 (* x 8.13008))) (- 0.15 (sqrt (+ (pow (+ 3.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.401) 2))))) (- (sqrt (+ (pow (+ 3.85 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.401) 2))) 0.25)) (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (- (* x 8.13008) 1.943)) (- 1.843 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (- (* x 11.6144) 3.18286)) (- 2.63286 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (+ 4.1 (* y 8.13008)) 2) (pow (- (* x 14.518) 3.97857) 2))))) (- (sqrt (+ (pow (+ 4.1 (* y 8.13008)) 2) (pow (- (* x 11.6144) 3.18286) 2))) 0.55)) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (- (* x 8.13008) 6.981)) (- 6.881 (* x 8.13008))) (- (sqrt (+ (pow (+ 3.4 (* y 8.13008)) 2) (pow (- (* x 8.13008) 6.931) 2))) 0.075)))) (fmin (fmin (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- 6.656 (* x 8.13008))) (- (* x 8.13008) 6.756)) (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.65 (* y 8.13008)))) (- 6.48101 (* x 8.13008))) (- (* x 8.13008) 6.656))) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 4 (* y 8.13008))) (- 6.48101 (* x 8.13008))) (- (* x 8.13008) 6.656)) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- 5.756 (* x 8.13008))) (- (* x 8.13008) 6.481)) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- 6.48101 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- 6.48101 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.775 (* y 8.13008))) (- (* x 8.13008) 5.431)) (- 5.331 (* x 8.13008)))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (- (* x 8.13008) 4.981)) (- 4.881 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (* x 8.13008) 5.431)) (- 4.881 (* x 8.13008))) (- (+ 3.775 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.156) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.156) 2))) 0.275))) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.75 (* y 8.13008))) (- (* x 8.13008) 4.761)) (- 4.661 (* x 8.13008))) (fmin (fmax (fmin (fmax (fmax (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.167999) 2)))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.167999) 2))) 0.275)) (- (fmin (fmax (fmax (+ 2.48625 (* y 5.28455)) (- (* x 2.23577) (+ 0.750575 (* y 1.21951)))) (- (+ (+ 1.91443 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (- (+ 2.48625 (* y 5.28455))) (+ (+ 1.91443 (* x 2.23577)) (* y 4.06504))) (- (+ 0.750575 (* y 1.21951)) (* x 2.23577)))))) (fmax (fmax (fmax (+ 3.785 (* y 8.13008)) (- (+ 3.875 (* y 8.13008)))) (- (* x 8.13008) 0.443)) (- (+ 0.0570004 (* x 8.13008))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.167999) 2))) 0.275)) (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 0.482 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 0.207 (* x 8.13008)) (- (+ 0.707 (* x 8.13008)))) (+ 3.785 (* y 8.13008))) (- (+ 3.875 (* y 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 0.482 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (* x 2.23577) (+ 0.571825 (* y 1.21951))) (- (+ (+ 2.09318 (* x 2.23577)) (* y 4.06504)))) (+ 2.48625 (* y 5.28455))) (fmax (fmax (+ (+ 2.09318 (* x 2.23577)) (* y 4.06504)) (- (+ 0.571825 (* y 1.21951)) (* x 2.23577))) (- (+ 2.48625 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 0.482 (* x 8.13008)) 2)))))))))) (fmin (fmin (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- (+ 0.957 (* x 8.13008)))) (+ 0.857 (* x 8.13008))) (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.65 (* y 8.13008)))) (- (+ 1.132 (* x 8.13008)))) (+ 0.957 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 4 (* y 8.13008))) (- (+ 1.132 (* x 8.13008)))) (+ 0.957 (* x 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- (+ 1.857 (* x 8.13008)))) (+ 1.132 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 1.132 (* x 8.13008))) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 1.132 (* x 8.13008))) 2))) 0.275)) (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (- (+ 2.282 (* x 8.13008)))) (+ 2.182 (* x 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.65 (* y 8.13008)))) (+ 2.282 (* x 8.13008))) (- (+ 2.457 (* x 8.13008)))) (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 4 (* y 8.13008))) (+ 2.282 (* x 8.13008))) (- (+ 2.457 (* x 8.13008))))) (fmin (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.468) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 3.785 (* y 8.13008)) (- (+ 3.875 (* y 8.13008)))) (- (* x 8.13008) 1.743)) (- 1.243 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.468) 2))) 0.275) (- (fmin (fmax (fmax (+ 2.48625 (* y 5.28455)) (- (* x 2.23577) (+ 1.10808 (* y 1.21951)))) (- (+ (+ 1.55693 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (+ (+ 1.55693 (* x 2.23577)) (* y 4.06504)) (- (+ 1.10808 (* y 1.21951)) (* x 2.23577))) (- (+ 2.48625 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.468) 2))))))) (fmin (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 4.475 (* y 8.13008)))) (- (* x 8.13008) 0.643001)) (- 0.543 (* x 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.818) 2)))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.818) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (sqrt (+ (pow (+ 4.937 (* x 8.13008)) 2) (pow (+ 1.34167 (* y 2.71003)) 2))) 0.075) (- (fmin (fmax (fmax (- (+ 1.3292 (* x 2.84553)) (* y 0.813008)) (- (+ (+ (* x 1.82927) 3.2527) (* y 4.06504)))) (- (+ 1.726 (* y 4.87805)) (* x 1.01626))) (fmax (fmax (- (* x 1.01626) (+ 1.726 (* y 4.87805))) (+ (+ (* x 1.82927) 3.2527) (* y 4.06504))) (- (* y 0.813008) (+ 1.3292 (* x 2.84553))))))) (+ 4.025 (* y 8.13008))) (- (+ 4.25 (* y 8.13008)))) (+ 4.862 (* x 8.13008))) (- (+ 5.012 (* x 8.13008)))) (fmin (- (sqrt (+ (pow (+ 4.025 (* y 8.13008)) 2) (pow (+ 4.912 (* x 8.13008)) 2))) 0.075) (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.825 (* y 8.13008))) (+ 5.162 (* x 8.13008))) (- (+ 5.262 (* x 8.13008)))))) (fmin (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (+ 5.612 (* x 8.13008))) (- (+ 5.712 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.825 (* y 8.13008)))) (+ 5.162 (* x 8.13008))) (- (+ 5.712 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 5.437 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 5.437 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 3.85 (* y 8.13008)))) (+ 5.962 (* x 8.13008))) (- (+ 6.062 (* x 8.13008)))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 3.45 (* y 8.13008)) (- (+ 3.55 (* y 8.13008)))) (+ 5.812 (* x 8.13008))) (- (+ 6.212 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.85 (* y 8.13008))) (+ 5.812 (* x 8.13008))) (- (+ 6.212 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 3.85 (* y 8.13008)) 2) (pow (+ 5.812 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 3.85 (* y 8.13008)) 2) (pow (+ 5.812 (* x 8.13008)) 2))) 0.25))) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (+ 6.57 (* x 8.13008))) (- (+ 6.67 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 3.1 (* y 8.13008)) (- (+ 4.1 (* y 8.13008)))) (+ 2.457 (* x 8.13008))) (- (+ 3.182 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 2.457 (* x 8.13008))) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 2.457 (* x 8.13008))) 2))) 0.275)) (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.775 (* y 8.13008))) (+ 2.807 (* x 8.13008))) (- (+ 2.907 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (+ 3.257 (* x 8.13008))) (- (+ 3.357 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 3.55 (* y 8.13008)) (- (+ 3.775 (* y 8.13008)))) (+ 2.807 (* x 8.13008))) (- (+ 3.357 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 3.082 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 3.082 (* x 8.13008)) 2))) 0.275))) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.75 (* y 8.13008))) (+ 3.477 (* x 8.13008))) (- (+ 3.577 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 2.66557 (* x 8.13008)) (* y 2.32288)) 2)))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 2.66557 (* x 8.13008)) (* y 2.32288)) 2))) 0.275)) (fmax (fmin (fmax (fmax (- (sqrt (+ (pow (+ 2.725 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.9605) 2))) 0.275) (- (fmin (fmax (fmax (+ 1.77125 (* y 5.28455)) (- (* x 2.23577) (+ (* y 1.21951) 2.17851))) (- 0.228514 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 0.228514) (- (+ (* y 1.21951) 2.17851) (* x 2.23577))) (- (+ 1.77125 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 2.725 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.9605) 2))))) (fmax (fmax (fmax (+ 2.685 (* y 8.13008)) (- (+ 2.775 (* y 8.13008)))) (- (* x 8.13008) 6.23551)) (- 5.73551 (* x 8.13008)))) (- (sqrt (+ (pow (+ 2.725 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.9605) 2))) 0.275)))))) (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 3 (* y 8.13008))) (- (* x 8.13008) 5.5355)) (- 5.4355 (* x 8.13008))) (+ 2.725 (* y 8.13008))) (fmax (fmax (fmax (- (+ 3 (* y 8.13008))) (+ 2 (* y 8.13008))) (- (* x 8.13008) 5.0855)) (- 4.9855 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 5.5355) (- 4.9855 (* x 8.13008))) (+ 2.45 (* y 8.13008))) (- (+ 2.725 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 5.2605) 2) (pow (+ 2.725 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (- (* x 8.13008) 5.2605) 2) (pow (+ 2.725 (* y 8.13008)) 2))) 0.275)) (fmin (fmax (fmax (fmax (+ 2 (* y 8.13008)) (- (+ 2.75 (* y 8.13008)))) (- (* x 8.13008) 4.7355)) (- 4.6355 (* x 8.13008))) (fmax (fmax (fmax (+ 2.35 (* y 8.13008)) (- (+ 2.45 (* y 8.13008)))) (- (* x 8.13008) 4.8855)) (- 4.4855 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 3 (* y 8.13008))) (+ 2.75 (* y 8.13008))) (- (* x 8.13008) 4.8855)) (- 4.4855 (* x 8.13008))) (- 0.15 (sqrt (+ (pow (+ 2.75 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.8855) 2))))) (- (sqrt (+ (pow (+ 2.75 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.8855) 2))) 0.25)) (fmin (fmax (fmax (fmax (- (+ 3 (* y 8.13008))) (+ 2.675 (* y 8.13008))) (- (* x 8.13008) 3.6855)) (- 3.5855 (* x 8.13008))) (fmax (fmax (fmax (- (+ 3 (* y 8.13008))) (+ 2.45 (* y 8.13008))) (- (* x 8.13008) 3.2355)) (- 3.1355 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.55 (* y 8.13008))) (+ 8.97857 (* x 11.6144))) (- (+ 9.52857 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 4.1 (* y 8.13008)) 2) (pow (+ 11.2232 (* x 14.518)) 2))))) (- (sqrt (+ (pow (+ 4.1 (* y 8.13008)) 2) (pow (+ 8.97857 (* x 11.6144)) 2))) 0.55)) (fmin (fmax (fmax (fmax (- (+ 4.1 (* y 8.13008))) (+ 3.75 (* y 8.13008))) (+ 6.79 (* x 8.13008))) (- (+ 6.89 (* x 8.13008)))) (fmax (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 5.97857 (* x 8.13008)) (* y 2.32288)) 2)))) (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (- (+ 5.97857 (* x 8.13008)) (* y 2.32288)) 2))) 0.275))))))) (fmin (fmin (fmin (fmin (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 7.725 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 3.785 (* y 8.13008)) (- (+ 3.875 (* y 8.13008)))) (+ 7.45 (* x 8.13008))) (- (+ 7.95 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 7.725 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (+ 2.48625 (* y 5.28455)) (- (+ 1.42 (* x 2.23577)) (* y 1.21951))) (- (+ (+ (* x 2.23577) (* y 4.06504)) 4.085))) (fmax (fmax (- (+ 2.48625 (* y 5.28455))) (+ (+ (* x 2.23577) (* y 4.06504)) 4.085)) (- (* y 1.21951) (+ 1.42 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (+ 3.825 (* y 8.13008)) 2) (pow (+ 7.725 (* x 8.13008)) 2))))))) (fmax (fmax (- (* y 1.82927) (+ 1.44223 (* x 4.47154))) (- (+ 1.30723 (* x 4.47154)) (* y 2.64228))) (+ 0.08 (* y 0.813008)))) (fmin (fmax (fmax (- (+ 1.44223 (* x 4.47154)) (* y 1.82927)) (- (+ 0.08 (* y 0.813008)))) (- (* y 2.64228) (+ 1.30723 (* x 4.47154)))) (fmin (fmax (fmax (- (+ 0.08 (* y 0.813008))) (- (* y 2.64228) (+ 1.36223 (* x 4.47154)))) (- (+ 1.38723 (* x 4.47154)) (* y 1.82927))) (fmax (fmax (+ 0.08 (* y 0.813008)) (- (+ 1.36223 (* x 4.47154)) (* y 2.64228))) (- (* y 1.82927) (+ 1.38723 (* x 4.47154))))))) (fmin (fmin (fmax (fmax (- (* y 1.82927) (+ 1.44223 (* x 4.47154))) (- (+ 1.36223 (* x 4.47154)) (* y 2.64228))) (+ 0.025 (* y 0.813008))) (fmax (fmax (- (+ 1.44223 (* x 4.47154)) (* y 1.82927)) (- (* y 2.64228) (+ 1.36223 (* x 4.47154)))) (- (+ 0.025 (* y 0.813008))))) (fmin (fmax (fmax (- (+ 0.08 (* y 0.813008))) (- (+ (+ 1.80223 (* y 1.82927)) (* x 4.47154)))) (+ (+ 1.82723 (* y 2.64228)) (* x 4.47154))) (fmin (fmax (fmax (+ 0.08 (* y 0.813008)) (+ (+ 1.80223 (* y 1.82927)) (* x 4.47154))) (- (+ (+ 1.82723 (* y 2.64228)) (* x 4.47154)))) (fmax (fmax (+ 0.025 (* y 0.813008)) (- (+ (+ 1.82723 (* y 2.64228)) (* x 4.47154)))) (+ (+ 1.74723 (* y 1.82927)) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (- (+ 0.025 (* y 0.813008))) (+ (+ 1.82723 (* y 2.64228)) (* x 4.47154))) (- (+ (+ 1.74723 (* y 1.82927)) (* x 4.47154)))) (fmax (fmax (fmax (+ 3.5325 (* x 8.13008)) (- (+ 3.6325 (* x 8.13008)))) (+ 0.25 (* y 8.13008))) (- (+ 0.8 (* y 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 4.63929 (* x 11.6144)) (- (+ 5.18929 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 5.79911 (* x 14.518)) 2) (pow (+ 0.8 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 4.63929 (* x 11.6144)) 2) (pow (+ 0.8 (* y 8.13008)) 2))) 0.55)) (+ 0.25 (* y 8.13008))) (- (+ 0.8 (* y 8.13008)))) (fmin (fmax (fmax (fmax (+ 3.7575 (* x 8.13008)) (- (+ 3.8575 (* x 8.13008)))) (+ 0.25 (* y 8.13008))) (- (+ 0.8 (* y 8.13008)))) (- (sqrt (+ (pow (+ 3.8075 (* x 8.13008)) 2) (pow (+ 0.0999999 (* y 8.13008)) 2))) 0.075)))) (fmin (fmin (fmax (fmax (fmax (+ 4.0025 (* x 8.13008)) (- (+ 4.1025 (* x 8.13008)))) (- (+ 0.8 (* y 8.13008)))) (+ 0.45 (* y 8.13008))) (fmin (fmax (fmax (fmax (- (* x 8.13008) 0.0154991) (- (+ 0.084501 (* x 8.13008)))) (- (+ 0.8 (* y 8.13008)))) (+ 0.45 (* y 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (- (+ 0.11593 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 0.525 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (+ 0.11593 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 0.525 (* y 8.13008)) 2))) 0.275)))) (fmin (fmax (fmax (fmax (+ 0.6945 (* x 8.13008)) (- (+ 0.794501 (* x 8.13008)))) (+ 0.525 (* y 8.13008))) (- (+ 0.8 (* y 8.13008)))) (fmin (fmax (fmax (fmax (+ 1.1445 (* x 8.13008)) (- (+ 1.2445 (* x 8.13008)))) (- (* y 8.13008) 0.2)) (- (+ 0.8 (* y 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 0.6945 (* x 8.13008)) (- (+ 1.2445 (* x 8.13008)))) (- (+ 0.525 (* y 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 0.969501 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 0.969501 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))) 0.275)) (+ 0.25 (* y 8.13008)))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (- (fmin (fmax (fmax (- (* y 2.23577) (+ 0.289485 (* x 2.27642))) (+ 0.707348 (* x 4.5122))) (- (+ 0.570488 (* (+ x y) 2.23577)))) (fmax (fmax (+ 0.570488 (* (+ x y) 2.23577)) (- (+ 0.707348 (* x 4.5122)))) (- (+ 0.289485 (* x 2.27642)) (* y 2.23577))))) (- 0.175 (sqrt (+ (pow (+ 1.5495 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 1.5495 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))) 0.275)) (fmax (fmax (- (+ 0.135 (* y 0.813008))) (- (+ 1.38723 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 1.30723 (* x 4.47154))))) (fmin (fmax (fmax (+ 0.135 (* y 0.813008)) (- (+ 1.30723 (* x 4.47154)) (* y 2.64228))) (- (* y 1.82927) (+ 1.38723 (* x 4.47154)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 0.525 (* y 8.13008))) (+ 6.25 (* x 8.13008))) (- (+ 6.8 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 6.525 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 6.525 (* x 8.13008)) 2) (pow (+ 0.525 (* y 8.13008)) 2))) 0.275)) (+ 0.25 (* y 8.13008))) (fmax (fmax (fmax (- (+ 0.55 (* y 8.13008))) (+ 7.05 (* x 8.13008))) (- (+ 7.15 (* x 8.13008)))) (- (* y 8.13008) 0.2))))) (fmin (fmin (fmax (fmax (fmax (+ 6.9 (* x 8.13008)) (- (+ 7.3 (* x 8.13008)))) (- (+ 0.25 (* y 8.13008)))) (+ 0.15 (* y 8.13008))) (fmax (fmax (fmax (fmax (fmax (+ 0.55 (* y 8.13008)) (+ 6.9 (* x 8.13008))) (- (+ 7.3 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 0.55 (* y 8.13008)) 2) (pow (+ 6.9 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (+ 0.55 (* y 8.13008)) 2) (pow (+ 6.9 (* x 8.13008)) 2))) 0.25)) (- (+ 0.8 (* y 8.13008))))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.55 (* y 8.13008))) (- (* x 8.13008) 7.72551)) (- 7.62551 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.95) (- 0.85 (* y 8.13008))) (- (* x 8.13008) 7.87551)) (- 7.47551 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 7.87551) (- 7.47551 (* x 8.13008))) (- (* y 8.13008) 0.55)) (- 0.3 (* y 8.13008))) (- 0.15 (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (- (* x 8.13008) 7.87551) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (- (* x 8.13008) 7.87551) 2))) 0.25)))))) (fmin (fmin (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.65)) (- (* x 8.13008) 7.35551)) (- 7.25551 (* x 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) (+ (* y 2.32288) 6.90979)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) (+ (* y 2.32288) 6.90979)) 2))) 0.275))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (+ 4.13393 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 0.525 (* y 8.13008)) 2)))) (- (sqrt (+ (pow (- (+ 4.13393 (* x 8.13008)) (* y 2.32288)) 2) (pow (+ 0.525 (* y 8.13008)) 2))) 0.275)) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (+ 3.7375 (* x 5.42005))) (+ 3.575 (* x 5.42005))) (- (+ 0.575 (* y 8.13008)))) (+ 0.4125 (* y 8.13008))) (- (sqrt (+ (pow (- (+ 2.49333 (* x 3.61337))) 2) (pow (- (+ 0.415 (* y 8.13008))) 2))) 0.0625))) (- (sqrt (+ (pow (- (+ 3.7375 (* x 5.42005))) 2) (pow (- (+ 0.4125 (* y 8.13008))) 2))) 0.1625)) (fmax (- (fmin (fmax (fmax (fmax (+ 3.7375 (* x 5.42005)) (- (+ 3.9 (* x 5.42005)))) (+ 0.475 (* y 8.13008))) (- (+ 0.6375 (* y 8.13008)))) (- (sqrt (+ (pow (+ 2.49 (* x 3.61337)) 2) (pow (+ 0.635 (* y 8.13008)) 2))) 0.0625))) (- (sqrt (+ (pow (+ 3.7375 (* x 5.42005)) 2) (pow (+ 0.6375 (* y 8.13008)) 2))) 0.1625))))) (fmin (fmin (fmax (fmax (fmax (- (+ 6.075 (* x 8.13008))) (+ 5.975 (* x 8.13008))) (+ 0.25 (* y 8.13008))) (- (+ 0.8 (* y 8.13008)))) (fmin (- (sqrt (+ (pow (+ 6.025 (* x 8.13008)) 2) (pow (+ 0.0999999 (* y 8.13008)) 2))) 0.075) (fmax (fmax (fmax (+ 6.25 (* x 8.13008)) (- (+ 6.35 (* x 8.13008)))) (+ 0.525 (* y 8.13008))) (- (+ 0.8 (* y 8.13008)))))) (fmin (fmax (fmax (fmax (+ 6.7 (* x 8.13008)) (- (+ 6.8 (* x 8.13008)))) (- (* y 8.13008) 0.2)) (- (+ 0.8 (* y 8.13008)))) (fmin (fmax (- (fmin (- (sqrt (+ (pow (- (* y 8.13008) 0.465) 2) (pow (- (* x 3.61337) 2.02467) 2))) 0.0625) (fmax (fmax (fmax (- (* y 8.13008) 0.625) (- 0.4625 (* y 8.13008))) (- (* x 5.42005) 3.0345)) (- 2.872 (* x 5.42005))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.4625) 2) (pow (- (* x 5.42005) 3.0345) 2))) 0.1625)) (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 3.933) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.615) (- 0.525 (* y 8.13008))) (- (* x 8.13008) 4.208)) (- 3.708 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 3.933) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 0.37375) (- (* x 2.23577) (+ 1.12595 (* y 1.21951)))) (- 1.32095 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 1.32095) (- (+ 1.12595 (* y 1.21951)) (* x 2.23577))) (- 0.37375 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 3.933) 2)))))))))))) (fmin (fmin (fmin (fmin (fmax (- (fmin (fmax (fmax (fmax (- 2.2095 (* x 5.42005)) (- (* x 5.42005) 2.372)) (- 0.525 (* y 8.13008))) (- (* y 8.13008) 0.6875)) (- (sqrt (+ (pow (- 1.47133 (* x 3.61337)) 2) (pow (- 0.685 (* y 8.13008)) 2))) 0.0625))) (- (sqrt (+ (pow (- 2.2095 (* x 5.42005)) 2) (pow (- 0.6875 (* y 8.13008)) 2))) 0.1625)) (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.575)) (- (* x 8.13008) 6.6455)) (- 6.5455 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.3 (* y 8.13008))) (- (* x 8.13008) 6.19551)) (- 6.0955 (* x 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 6.6455) (- 6.0955 (* x 8.13008))) (- (* y 8.13008) 0.85)) (- 0.575 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 6.3705) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 6.3705) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.55 (* y 8.13008))) (- (* x 8.13008) 5.8455)) (- 5.7455 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.95) (- 0.85 (* y 8.13008))) (- (* x 8.13008) 5.9955)) (- 5.5955 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.55) (- 0.3 (* y 8.13008))) (- (* x 8.13008) 5.9955)) (- 5.5955 (* x 8.13008))) (- 0.15 (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (- (* x 8.13008) 5.9955) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (- (* x 8.13008) 5.9955) 2))) 0.25))) (fmin (fmax (- (fmin (fmax (fmax (fmax (- 0.525 (* y 8.13008)) (- (* y 8.13008) 0.6875)) (- 3.0345 (* x 5.42005))) (- (* x 5.42005) 3.197)) (- (sqrt (+ (pow (- 0.685 (* y 8.13008)) 2) (pow (- 2.02133 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- 0.6875 (* y 8.13008)) 2) (pow (- 3.0345 (* x 5.42005)) 2))) 0.1625)) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.625) (- 0.4625 (* y 8.13008))) (- 0.788667 (* x 5.42005))) (- (* x 5.42005) 0.951167)) (- (sqrt (+ (pow (- (* y 8.13008) 0.465) 2) (pow (- (* x 3.61337) 0.635778) 2))) 0.0625))) (- (sqrt (+ (pow (- (* y 8.13008) 0.4625) 2) (pow (- (* x 5.42005) 0.951167) 2))) 0.1625)) (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (- (* x 8.13008) 0.125)) (- 0.0249996 (* x 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (- (* x 11.6144) 0.585714)) (- 0.0357141 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 0.3) 2) (pow (- (* x 14.518) 0.732143) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.3) 2) (pow (- (* x 11.6144) 0.585714) 2))) 0.55)) (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (+ 0.1 (* x 8.13008))) (- (+ 0.2 (* x 8.13008))))) (fmin (- (sqrt (+ (pow (- (* y 8.13008) 1) 2) (pow (+ 0.150001 (* x 8.13008)) 2))) 0.075) (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 0.6 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.615) (- 0.525 (* y 8.13008))) (+ 0.325 (* x 8.13008))) (- (+ 0.825 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 0.6 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 0.37375) (- (+ 0.120625 (* x 2.23577)) (* y 1.21951))) (- 0.0743749 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 0.37375 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 0.0743749)) (- (* y 1.21951) (+ 0.120625 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 0.6 (* x 8.13008)) 2))))))) (fmax (- (fmin (fmax (fmax (fmax (- (* x 5.42005) 2.2095) (- 2.047 (* x 5.42005))) (- (* y 8.13008) 0.625)) (- 0.4625 (* y 8.13008))) (- (sqrt (+ (pow (- (* y 8.13008) 0.465) 2) (pow (- (* x 3.61337) 1.47467) 2))) 0.0625))) (- (sqrt (+ (pow (- (* y 8.13008) 0.4625) 2) (pow (- (* x 5.42005) 2.2095) 2))) 0.1625))))) (fmin (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.625)) (- (* x 8.13008) 2.9705)) (- 2.8705 (* x 8.13008))) (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (- (* x 8.13008) 2.5205)) (- 2.4205 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- (* x 8.13008) 2.9705)) (- 2.4205 (* x 8.13008))) (- 0.625 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 2.6955) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 2.6955) 2))) 0.275)))) (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 2.0455) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.615) (- 0.525 (* y 8.13008))) (- (* x 8.13008) 2.3205)) (- 1.8205 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 2.0455) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 0.37375) (- (* x 2.23577) (+ 0.606888 (* y 1.21951)))) (- 0.801888 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 0.37375 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 0.801888)) (- (+ 0.606888 (* y 1.21951)) (* x 2.23577)))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (* x 8.13008) 2.0455) 2))))))) (fmin (fmax (- (fmin (fmax (fmax (fmax (- 0.525 (* y 8.13008)) (- (* y 8.13008) 0.6875)) (- 0.951167 (* x 5.42005))) (- (* x 5.42005) 1.11367)) (- (sqrt (+ (pow (- 0.685 (* y 8.13008)) 2) (pow (- 0.632445 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- 0.6875 (* y 8.13008)) 2) (pow (- 0.951167 (* x 5.42005)) 2))) 0.1625)) (fmax (- (sqrt (+ (pow (- (+ 1.5125 (* y 8.13008))) 2) (pow (- (+ 0.3955 (* x 5.42005))) 2))) 0.1625) (- (fmin (fmax (fmax (fmax (- (+ 1.675 (* y 8.13008))) (+ 1.5125 (* y 8.13008))) (- (+ 0.3955 (* x 5.42005)))) (+ 0.233001 (* x 5.42005))) (- (sqrt (+ (pow (- (+ 1.515 (* y 8.13008))) 2) (pow (- (+ 0.265334 (* x 3.61337))) 2))) 0.0625)))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (- (fmin (fmax (fmax (fmax (+ 1.575 (* y 8.13008)) (- (+ 1.7375 (* y 8.13008)))) (+ 0.395501 (* x 5.42005))) (- (+ 0.558001 (* x 5.42005)))) (- (sqrt (+ (pow (+ 1.735 (* y 8.13008)) 2) (pow (+ 0.262 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (+ 1.7375 (* y 8.13008)) 2) (pow (+ 0.395501 (* x 5.42005)) 2))) 0.1625)) (fmax (fmax (- (+ 0.245 (* y 0.813008))) (- (+ 0.596601 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 0.4066 (* x 4.47154))))) (fmin (fmax (fmax (+ 0.245 (* y 0.813008)) (- (+ 0.4066 (* x 4.47154)) (* y 2.64228))) (- (* y 1.82927) (+ 0.596601 (* x 4.47154)))) (fmin (fmax (fmax (+ 0.19 (* y 0.813008)) (- (+ 0.4066 (* x 4.47154)) (* y 2.64228))) (- (* y 1.82927) (+ 0.6516 (* x 4.47154)))) (fmax (fmax (- (+ 0.19 (* y 0.813008))) (- (* y 2.64228) (+ 0.4066 (* x 4.47154)))) (- (+ 0.6516 (* x 4.47154)) (* y 1.82927)))))) (fmin (fmin (fmax (fmax (- (+ 0.19 (* y 0.813008))) (- (+ 0.5966 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 0.461601 (* x 4.47154)))) (fmax (fmax (+ 0.19 (* y 0.813008)) (- (* y 1.82927) (+ 0.596601 (* x 4.47154)))) (- (+ 0.461601 (* x 4.47154)) (* y 2.64228)))) (fmin (fmax (fmax (+ 0.135 (* y 0.813008)) (- (* y 1.82927) (+ 0.6516 (* x 4.47154)))) (- (+ 0.461601 (* x 4.47154)) (* y 2.64228))) (fmin (fmax (fmax (- (+ 0.135 (* y 0.813008))) (- (+ 0.6516 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 0.461601 (* x 4.47154)))) (fmax (fmax (- (+ 0.19 (* y 0.813008))) (- (+ (+ 1.5066 (* y 1.82927)) (* x 4.47154)))) (+ (+ 1.6416 (* y 2.64228)) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (+ 0.19 (* y 0.813008)) (- (+ (+ 1.6416 (* y 2.64228)) (* x 4.47154)))) (+ (+ 1.5066 (* y 1.82927)) (* x 4.47154))) (fmax (fmax (+ 0.135 (* y 0.813008)) (- (+ (+ 1.6416 (* y 2.64228)) (* x 4.47154)))) (+ (+ 1.4516 (* y 1.82927)) (* x 4.47154)))) (fmin (fmax (fmax (- (+ 0.135 (* y 0.813008))) (+ (+ 1.6416 (* y 2.64228)) (* x 4.47154))) (- (+ (+ 1.4516 (* y 1.82927)) (* x 4.47154)))) (fmin (fmax (fmax (fmax (+ 1.35 (* y 8.13008)) (- (+ 1.45 (* y 8.13008)))) (- (* x 8.13008) 1.3305)) (- 0.9305 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (+ 0.9 (* y 8.13008)) (- (+ 1.15 (* y 8.13008)))) (- (* x 8.13008) 1.3305)) (- 0.9305 (* x 8.13008))) (- 0.15 (sqrt (+ (pow (+ 1.15 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.3305) 2))))) (- (sqrt (+ (pow (+ 1.15 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.3305) 2))) 0.25))))) (fmin (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.55 (* y 8.13008))) (- (* x 8.13008) 0.8105)) (- 0.7105 (* x 8.13008))) (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) (+ 0.993357 (* y 2.32288))) 2)))) (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (* x 8.13008) (+ 0.993357 (* y 2.32288))) 2))) 0.275)) (fmax (fmax (fmax (+ 0.9 (* y 8.13008)) (- (+ 1.65 (* y 8.13008)))) (- (* x 8.13008) 0.000499725)) (- (+ 0.0995007 (* x 8.13008)))))) (fmin (fmax (fmax (fmax (+ 1.25 (* y 8.13008)) (- (+ 1.35 (* y 8.13008)))) (- (* x 8.13008) 0.150499)) (- (+ 0.249501 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.65 (* y 8.13008))) (- (* x 8.13008) 0.150499)) (- (+ 0.249501 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 1.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.150499) 2))))) (- (sqrt (+ (pow (+ 1.65 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.150499) 2))) 0.25)) (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 0.9 (* y 8.13008))) (- (fmin (fmin (fmin (fmax (fmax (- (+ 1.66785 (* x 4.47154)) (* y 3.25203)) (- (+ 2.24785 (* x 4.47154)))) (+ 0.36 (* y 3.25203))) (fmax (fmax (+ 2.24785 (* x 4.47154)) (- (* y 3.25203) (+ 1.66785 (* x 4.47154)))) (- (+ 0.36 (* y 3.25203))))) (fmin (fmax (fmax (+ 0.45 (* y 4.06504)) (+ 1.7935 (* x 4.06504))) (- (+ 2.4935 (* (+ x y) 4.06504)))) (fmax (fmax (+ 2.4935 (* (+ x y) 4.06504)) (- (+ 1.7935 (* x 4.06504)))) (- (+ 0.45 (* y 4.06504)))))) (fmin (fmin (fmax (fmax (- (+ 3.497 (* x 8.13008)) (* y 0.813008)) (- (+ (+ 1.89845 (* y 2.60163)) (* x 2.84553)))) (- (* y 3.41463) (+ 1.95355 (* x 5.28455)))) (fmax (fmax (- (+ 1.95355 (* x 5.28455)) (* y 3.41463)) (+ (+ 1.89845 (* y 2.60163)) (* x 2.84553))) (- (* y 0.813008) (+ 3.497 (* x 8.13008))))) (fmin (fmax (fmax (- (+ 0.54 (* y 2.19512))) (- (+ 1.43045 (* x 2.84553)))) (+ (+ 1.87595 (* y 2.19512)) (* x 2.84553))) (fmax (fmax (+ 0.54 (* y 2.19512)) (+ 1.43045 (* x 2.84553))) (- (+ (+ 1.87595 (* y 2.19512)) (* x 2.84553))))))))) (+ 3.687 (* x 8.13008))) (- (+ 4.187 (* x 8.13008))))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.55 (* y 8.13008))) (+ 4.307 (* x 8.13008))) (- (+ 4.407 (* x 8.13008)))) (fmax (- 0.175 (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (+ 4.12414 (* x 8.13008)) (* y 2.32288)) 2)))) (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (- (+ 4.12414 (* x 8.13008)) (* y 2.32288)) 2))) 0.275))) (fmin (fmax (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (+ 5.242 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 1.585 (* y 8.13008)) (- (+ 1.675 (* y 8.13008)))) (+ 4.967 (* x 8.13008))) (- (+ 5.467 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (+ 5.242 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (+ 1.05625 (* y 5.28455)) (- (+ 1.06718 (* x 2.23577)) (* y 1.21951))) (- (+ (+ (* x 2.23577) 2.30217) (* y 4.06504)))) (fmax (fmax (+ (+ (* x 2.23577) 2.30217) (* y 4.06504)) (- (* y 1.21951) (+ 1.06718 (* x 2.23577)))) (- (+ 1.05625 (* y 5.28455))))))) (- 0.175 (sqrt (+ (pow (+ 1.625 (* y 8.13008)) 2) (pow (+ 5.242 (* x 8.13008)) 2))))))) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (+ 5.875 (* x 8.13008))) (- (+ 5.975 (* x 8.13008)))) (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 2.287 (* x 8.13008))) (- (+ 2.387 (* x 8.13008)))) (+ 1.55 (* y 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.55 (* y 8.13008))) (+ 2.537 (* x 8.13008))) (- (+ 2.637 (* x 8.13008)))) (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.375 (* y 8.13008))) (+ 2.787 (* x 8.13008))) (- (+ 2.887 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (+ 1.25 (* y 8.13008)) (- (+ 2.887 (* x 8.13008)))) (- (+ 1.55 (* y 8.13008)))) (+ 2.237 (* x 8.13008))) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 1.55 (* y 8.13008)) 2) (pow (+ 2.462 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (+ 1.55 (* y 8.13008)) 2) (pow (+ 2.462 (* x 8.13008)) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 1.55 (* y 8.13008)) 2) (pow (+ 2.712 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (+ 1.55 (* y 8.13008)) 2) (pow (+ 2.712 (* x 8.13008)) 2))) 0.175)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (fmin (fmax (fmax (- (* x 2.84553) (+ (* y 0.813008) 1.89365)) (- 0.681276 (+ (* x 1.82927) (* y 4.06504)))) (- (+ 1.01488 (* y 4.87805)) (* x 1.01626))) (fmax (fmax (- (* x 1.01626) (+ 1.01488 (* y 4.87805))) (- (+ (* x 1.82927) (* y 4.06504)) 0.681276)) (- (+ (* y 0.813008) 1.89365) (* x 2.84553))))) (+ 0.725 (* y 8.13008))) (- (+ 0.95 (* y 8.13008)))) (- (* x 8.13008) 5.289)) (- 5.139 (* x 8.13008))) (- (sqrt (+ (pow (+ 0.241667 (* y 2.71003)) 2) (pow (- (* x 8.13008) 5.214) 2))) 0.075)) (- (sqrt (+ (pow (+ 0.725 (* y 8.13008)) 2) (pow (- (* x 8.13008) 5.239) 2))) 0.075))))) (fmin (fmin (fmin (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (* x 8.13008) 4.781)) (- 4.681 (* x 8.13008))) (- (+ 0.8 (* y 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (* x 11.6144) 7.23715)) (- 6.68715 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (+ 0.8 (* y 8.13008)) 2) (pow (- (* x 14.518) 9.04643) 2))))) (- (sqrt (+ (pow (+ 0.8 (* y 8.13008)) 2) (pow (- (* x 11.6144) 7.23715) 2))) 0.55)) (- (+ 0.8 (* y 8.13008))))) (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 0.525 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.306) 2)))) (- (sqrt (+ (pow (+ 0.525 (* y 8.13008)) 2) (pow (- (* x 8.13008) 4.306) 2))) 0.275)) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 1.35 (* y 8.13008))) (+ 7.98571 (* x 11.6144))) (- (+ 8.53571 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (+ 1.9 (* y 8.13008)) 2) (pow (+ 9.98214 (* x 14.518)) 2))))) (- (sqrt (+ (pow (+ 1.9 (* y 8.13008)) 2) (pow (+ 7.98571 (* x 11.6144)) 2))) 0.55)) (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 0.9 (* y 8.13008))) (+ 6.5 (* x 8.13008))) (- (+ 6.6 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (+ 1.35 (* y 8.13008)) (+ 6.325 (* x 8.13008))) (- (+ 1.45 (* y 8.13008)))) (- (+ 6.5 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 6.325 (* x 8.13008))) (+ 1.8 (* y 8.13008))) (- (+ 6.5 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (- (+ 1.9 (* y 8.13008))) (+ 0.9 (* y 8.13008))) (+ 5.6 (* x 8.13008))) (- (+ 6.325 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (+ 1.625 (* y 8.13008)) 2))))) (- (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (+ 1.625 (* y 8.13008)) 2))) 0.275)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.2) (- (+ 0.8 (* y 8.13008)))) (- (* x 8.13008) 7.28901)) (- 7.18901 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.2) (- (+ 0.8 (* y 8.13008)))) (- (* x 8.13008) 7.03901)) (- 6.93901 (* x 8.13008))) (fmax (fmax (fmax (- (* y 8.13008) 4.76837e-7) (- (+ 0.25 (* y 8.13008)))) (- (* x 8.13008) 6.81401)) (- 6.71401 (* x 8.13008)))))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.2) (- 0.1 (* y 8.13008))) (- (* x 8.13008) 6.61401)) (- 6.11401 (* x 8.13008))) (fmax (fmax (fmax (- (+ 0.8 (* y 8.13008))) (- (* x 8.13008) 6.61401)) (- 6.11401 (* x 8.13008))) (+ 0.7 (* y 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.2) (- (+ 0.8 (* y 8.13008)))) (- (* x 8.13008) 6.41401)) (- 6.314 (* x 8.13008))) (fmin (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (+ 0.8 (* y 8.13008)))) (- (* x 8.13008) 2.1185)) (- 2.0185 (* x 8.13008))) (- (sqrt (+ (pow (+ 0.0999999 (* y 8.13008)) 2) (pow (- (* x 8.13008) 2.0685) 2))) 0.075)))) (fmin (fmin (fmax (fmax (fmax (- (+ 0.8 (* y 8.13008))) (+ 0.45 (* y 8.13008))) (- (* x 8.13008) 1.1935)) (- 1.0935 (* x 8.13008))) (fmax (fmax (fmax (- (+ 0.8 (* y 8.13008))) (+ 0.45 (* y 8.13008))) (- (* x 8.13008) 0.943501)) (- 0.8435 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 0.8 (* y 8.13008))) (+ 0.275 (* y 8.13008))) (- (* x 8.13008) 0.693501)) (- 0.5935 (* x 8.13008))) (fmin (fmax (fmax (fmax (fmax (- 0.5935 (* x 8.13008)) (+ 0.15 (* y 8.13008))) (- (+ 0.45 (* y 8.13008)))) (- (* x 8.13008) 1.2435)) (fmin (fmax (- 0.075 (sqrt (+ (pow (+ 0.45 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.0185) 2)))) (- (sqrt (+ (pow (+ 0.45 (* y 8.13008)) 2) (pow (- (* x 8.13008) 1.0185) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (+ 0.45 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.7685) 2)))) (- (sqrt (+ (pow (+ 0.45 (* y 8.13008)) 2) (pow (- (* x 8.13008) 0.7685) 2))) 0.175)))) (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (+ 0.8 (* y 8.13008)))) (- (* x 8.13008) 0.2355)) (- 0.1355 (* x 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (+ 0.0499997 (* y 8.13008)) (- (* x 8.13008) 3.781)) (- (+ 0.8 (* y 8.13008)))) (- 3.681 (* x 8.13008))) (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (+ 0.35 (* y 8.13008)))) (- (* x 8.13008) 3.931)) (- 3.531 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.2) (- (* x 8.13008) 3.931)) (- 3.531 (* x 8.13008))) (- (+ 0.0499997 (* y 8.13008)))) (- 0.15 (sqrt (+ (pow (+ 0.0499997 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.931) 2))))) (- (sqrt (+ (pow (+ 0.0499997 (* y 8.13008)) 2) (pow (- (* x 8.13008) 3.931) 2))) 0.25)) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (+ 0.575 (* y 8.13008))) (+ 0.4125 (* y 8.13008))) (- 1.65817 (* x 5.42005))) (- (* x 5.42005) 1.82067)) (- (sqrt (+ (pow (- (+ 0.415 (* y 8.13008))) 2) (pow (- 1.10378 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- (+ 0.4125 (* y 8.13008))) 2) (pow (- 1.65817 (* x 5.42005)) 2))) 0.1625)) (fmax (- (fmin (fmax (fmax (fmax (+ 0.475 (* y 8.13008)) (- (+ 0.6375 (* y 8.13008)))) (- (* x 5.42005) 1.65817)) (- 1.49567 (* x 5.42005))) (- (sqrt (+ (pow (+ 0.635 (* y 8.13008)) 2) (pow (- (* x 3.61337) 1.10711) 2))) 0.0625))) (- (sqrt (+ (pow (+ 0.6375 (* y 8.13008)) 2) (pow (- (* x 5.42005) 1.65817) 2))) 0.1625))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (+ 0.25 (* y 8.13008)) (- (+ 0.8 (* y 8.13008)))) (- (* x 11.6144) 0.743571)) (- 0.193571 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (+ 0.8 (* y 8.13008)) 2) (pow (- (* x 14.518) 0.929465) 2))))) (- (sqrt (+ (pow (+ 0.8 (* y 8.13008)) 2) (pow (- (* x 11.6144) 0.743571) 2))) 0.55)) 100000000.0) (fmin (fmax (- (sqrt (+ (pow (- (* x 8.13008) 7.12751) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 2.815) (- 2.725 (* y 8.13008))) (- (* x 8.13008) 7.40251)) (- 6.90251 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* x 8.13008) 7.12751) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 1.80375) (- (* x 2.23577) (+ (* y 1.21951) 1.67444))) (- 3.29944 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 3.29944) (- (+ (* y 1.21951) 1.67444) (* x 2.23577))) (- 1.80375 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 7.12751) 2) (pow (- (* y 8.13008) 2.775) 2))))))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- (* x 4.47154) (+ (* y 2.03252) 2.95138))) (- (+ 2.64638 (* y 2.84553)) (* x 4.47154))) (fmax (fmax (- (* x 4.47154) (+ 2.64638 (* y 2.84553))) (- (+ (* y 2.03252) 2.95138) (* x 4.47154))) (- (* y 0.813008) 0.25))))))) (fmin (fmin (fmin (fmin (fmax (fmax (- (* x 4.47154) (+ 2.64638 (* y 2.84553))) (- (* y 0.813008) 0.305)) (- (+ (* y 2.03252) 2.89638) (* x 4.47154))) (fmax (fmax (- (+ 2.64638 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ (* y 2.03252) 2.89638))) (- 0.305 (* y 0.813008)))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- 4.14638 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 4.45138)) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (- 4.45138 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 4.14638)) (fmax (fmax (- (* y 0.813008) 0.305) (- 4.45138 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 4.20138))))) (fmin (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 4.45138)) (- 4.20138 (+ (* y 2.03252) (* x 4.47154)))) (fmax (fmax (- (+ (+ (* y 2.03252) 2.8125) (* x 4.47154))) (+ (+ 2.6175 (* y 2.84553)) (* x 4.47154))) (- 0.14 (* y 0.813008)))) (fmin (fmax (fmax (+ (+ (* y 2.03252) 2.8125) (* x 4.47154)) (- (+ (+ 2.6175 (* y 2.84553)) (* x 4.47154)))) (- (* y 0.813008) 0.14)) (fmin (fmax (fmax (- (+ (+ 2.6175 (* y 2.84553)) (* x 4.47154))) (+ (+ (* y 2.03252) 2.7575) (* x 4.47154))) (- (* y 0.813008) 0.195)) (fmax (fmax (+ (+ 2.6175 (* y 2.84553)) (* x 4.47154)) (- (+ (+ (* y 2.03252) 2.7575) (* x 4.47154)))) (- 0.195 (* y 0.813008))))))) (fmin (fmin (fmin (fmax (- 0.175 (sqrt (+ (pow (+ 6.375 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.675) 2)))) (- (sqrt (+ (pow (+ 6.375 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.675) 2))) 0.275)) (fmax (fmax (fmax (+ 6.75 (* x 8.13008)) (- (+ 6.85 (* x 8.13008)))) (- (* y 8.13008) 1.725)) (- 1.4 (* y 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 7.3 (* x 8.13008))) (+ 7.2 (* x 8.13008))) (- 1.4 (* y 8.13008))) (- (* y 8.13008) 1.95)) (fmin (fmax (fmax (fmax (fmax (fmax (- (+ 7.3 (* x 8.13008))) (+ 6.75 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (+ 7.025 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.675) 2))))) (- (sqrt (+ (pow (+ 7.025 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.675) 2))) 0.275)) (- 1.725 (* y 8.13008))) (- (* y 8.13008) 1.95)) (fmax (fmax (fmax (- (* y 8.13008) 2.825) (- 2.5 (* y 8.13008))) (- (* x 8.13008) 8.05251)) (- 7.95251 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.05)) (- (* x 8.13008) 7.60251)) (- 7.50251 (* x 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 8.05251) (- (* y 8.13008) 3.05)) (- 7.50251 (* x 8.13008))) (- 2.825 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 7.77751) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 7.77751) 2))) 0.275)) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.775)) (- (* x 8.13008) 3.1225)) (- 3.0225 (* x 8.13008))))) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* x 8.13008) 2.6725)) (- 2.5725 (* x 8.13008))) (- (* y 8.13008) 3.5)) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 3.05) (- 2.5725 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 2.8475) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 2.8475) 2))) 0.275)) (- 2.775 (* y 8.13008))) (- (* x 8.13008) 3.1225)) (fmax (fmax (- 0.25 (* y 0.813008)) (- (* x 4.47154) (+ 0.597376 (* y 2.03252)))) (- (+ 0.292376 (* y 2.84553)) (* x 4.47154))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.25) (- (* x 4.47154) (+ 0.292376 (* y 2.84553)))) (- (+ 0.597376 (* y 2.03252)) (* x 4.47154))) (fmax (fmax (- (* y 0.813008) 0.305) (- (* x 4.47154) (+ 0.292376 (* y 2.84553)))) (- (+ 0.542376 (* y 2.03252)) (* x 4.47154)))) (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ 0.292376 (* y 2.84553)) (* x 4.47154))) (- (* x 4.47154) (+ 0.542376 (* y 2.03252)))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- 1.79238 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 2.09738)) (fmax (fmax (- (* y 0.813008) 0.25) (- 2.09738 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.79238))))) (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.305) (- 2.09738 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.84738)) (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 2.09738)) (- 1.84738 (+ (* y 2.03252) (* x 4.47154))))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- (* x 4.47154) (+ 0.322376 (* y 2.03252)))) (- (+ 0.0173756 (* y 2.84553)) (* x 4.47154))) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (- (* x 4.47154) (+ 0.0173756 (* y 2.84553)))) (- (+ 0.322376 (* y 2.03252)) (* x 4.47154))) (fmax (fmax (- (* y 0.813008) 0.305) (- (* x 4.47154) (+ 0.0173756 (* y 2.84553)))) (- (+ 0.267376 (* y 2.03252)) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ 0.0173756 (* y 2.84553)) (* x 4.47154))) (- (* x 4.47154) (+ 0.267376 (* y 2.03252)))) (fmax (fmax (- 0.25 (* y 0.813008)) (- 1.51738 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 1.82238))) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (- 1.82238 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.51738)) (fmin (fmax (fmax (- (* y 0.813008) 0.305) (- 1.82238 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.57238)) (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 1.82238)) (- 1.57238 (+ (* y 2.03252) (* x 4.47154))))))) (fmin (fmin (fmax (- (sqrt (+ (pow (- (* x 8.13008) 5.7775) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 2.815) (- 2.725 (* y 8.13008))) (- (* x 8.13008) 6.0525)) (- 5.5525 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* x 8.13008) 5.7775) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 1.80375) (- (* x 2.23577) (+ (* y 1.21951) 1.30319))) (- 2.92819 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 1.80375 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 2.92819)) (- (+ (* y 1.21951) 1.30319) (* x 2.23577)))))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 5.7775) 2) (pow (- (* y 8.13008) 2.775) 2))))))) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.775)) (- (* x 8.13008) 4.6525)) (- 4.5525 (* x 8.13008))) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.5)) (- (* x 8.13008) 4.2025)) (- 4.1025 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 3.05) (- (* x 8.13008) 4.6525)) (- 4.1025 (* x 8.13008))) (- 2.775 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 4.3775) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 4.3775) 2))) 0.275)) (fmin (fmax (fmax (- (fmin (fmax (fmax (- (+ 0.300176 (* y 2.23577)) (* x 2.27642)) (- (* x 4.5122) 2.26024)) (- 1.80744 (* (+ x y) 2.23577))) (fmax (fmax (- (* (+ x y) 2.23577) 1.80744) (- 2.26024 (* x 4.5122))) (- (* x 2.27642) (+ 0.300176 (* y 2.23577)))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 3.7975) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 3.7975) 2))) 0.275)) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.05)) (- (* x 8.13008) 3.3975)) (- 3.2975 (* x 8.13008)))))))) (fmin (fmin (fmin (fmin (- (sqrt (+ (pow (- (* y 8.13008) 3.2) 2) (pow (- (* x 8.13008) 3.3475) 2))) 0.075) (fmax (- (fmin (fmax (fmax (fmax (- 2.725 (* y 8.13008)) (- (* y 8.13008) 2.8875)) (- (+ 0.5175 (* x 5.42005)))) (+ 0.355 (* x 5.42005))) (- (sqrt (+ (pow (- 2.885 (* y 8.13008)) 2) (pow (- (+ 0.346667 (* x 3.61337))) 2))) 0.0625))) (- (sqrt (+ (pow (- 2.8875 (* y 8.13008)) 2) (pow (- (+ 0.5175 (* x 5.42005))) 2))) 0.1625))) (fmin (fmax (- (fmin (fmax (fmax (fmax (- (* y 8.13008) 2.825) (- 2.6625 (* y 8.13008))) (+ 0.5175 (* x 5.42005))) (- (+ 0.68 (* x 5.42005)))) (- (sqrt (+ (pow (- (* y 8.13008) 2.665) 2) (pow (+ 0.343334 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- (* y 8.13008) 2.6625) 2) (pow (+ 0.5175 (* x 5.42005)) 2))) 0.1625)) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.05)) (+ 1.12 (* x 8.13008))) (- (+ 1.22 (* x 8.13008)))) (fmax (fmax (fmax (- (* y 8.13008) 3.05) (- 2.775 (* y 8.13008))) (+ 1.57 (* x 8.13008))) (- (+ 1.67 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.775)) (+ 1.12 (* x 8.13008))) (- (+ 1.67 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 1.395 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 1.395 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.85)) (+ 1.77 (* x 8.13008))) (- (+ 1.87 (* x 8.13008))))) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.85)) (+ 2.02 (* x 8.13008))) (- (+ 2.12 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (fmin (fmax (fmax (- (+ 0.0958748 (* x 2.84553)) (* y 0.813008)) (- 1.26444 (+ (* x 1.82927) (* y 4.06504)))) (- (* y 4.87805) (+ (* x 1.01626) 1.55781))) (fmax (fmax (- (+ (* x 1.01626) 1.55781) (* y 4.87805)) (- (+ (* x 1.82927) (* y 4.06504)) 1.26444)) (- (* y 0.813008) (+ 0.095875 (* x 2.84553)))))) (- (* y 8.13008) 2.575)) (- 2.35 (* y 8.13008))) (- (* x 8.13008) 0.5475)) (- 0.3975 (* x 8.13008))) (- (sqrt (+ (pow (- (* y 2.71003) 0.858333) 2) (pow (- (* x 8.13008) 0.4725) 2))) 0.075)) (- (sqrt (+ (pow (- (* y 8.13008) 2.575) 2) (pow (- (* x 8.13008) 0.4975) 2))) 0.075))))) (fmin (fmin (fmin (fmax (fmax (- (fmin (fmax (fmax (- (* y 2.23577) (+ 0.737225 (* x 2.27642))) (- (* x 4.5122) 0.203962)) (- 0.788562 (* (+ x y) 2.23577))) (fmax (fmax (- (* (+ x y) 2.23577) 0.788562) (- 0.203962 (* x 4.5122))) (- (+ 0.737225 (* x 2.27642)) (* y 2.23577))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 0.0924997) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (* x 8.13008) 0.0924997) 2))) 0.275)) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.05)) (+ 0.3075 (* x 8.13008))) (- (+ 0.4075 (* x 8.13008))))) (fmin (- (sqrt (+ (pow (- (* y 8.13008) 3.2) 2) (pow (+ 0.357501 (* x 8.13008)) 2))) 0.075) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.5)) (+ 3.845 (* x 8.13008))) (- (+ 3.945 (* x 8.13008)))) (fmax (fmax (fmax (- (* y 8.13008) 2.825) (- 2.5 (* y 8.13008))) (+ 4.07 (* x 8.13008))) (- (+ 4.17 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.05)) (+ 4.52 (* x 8.13008))) (- (+ 4.62 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 3.05) (- 2.825 (* y 8.13008))) (+ 4.07 (* x 8.13008))) (- (+ 4.62 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 4.345 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 4.345 (* x 8.13008)) 2))) 0.275)) (fmax (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 4.995 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 2.815) (- 2.725 (* y 8.13008))) (+ 4.72 (* x 8.13008))) (- (+ 5.22 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 4.995 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 1.80375) (- (+ 1.65925 (* x 2.23577)) (* y 1.21951))) (- (+ (+ 0.03425 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (- 1.80375 (* y 5.28455)) (+ (+ 0.03425 (* x 2.23577)) (* y 4.06504))) (- (* y 1.21951) (+ 1.65925 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (+ 4.995 (* x 8.13008)) 2))))))))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- (+ 3.716 (* x 4.47154)) (* y 2.03252))) (- (* y 2.84553) (+ 4.021 (* x 4.47154)))) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (- (+ 4.021 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.716 (* x 4.47154)))) (fmax (fmax (- (* y 0.813008) 0.305) (- (+ 4.021 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.771 (* x 4.47154))))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (- (* y 2.84553) (+ 4.021 (* x 4.47154)))) (- (+ 3.771 (* x 4.47154)) (* y 2.03252))) (fmax (fmax (- 0.25 (* y 0.813008)) (- (+ (+ (* y 2.03252) 2.521) (* x 4.47154)))) (+ (+ 2.216 (* y 2.84553)) (* x 4.47154)))) (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.025)) (+ 2.27 (* x 8.13008))) (- (+ 2.37 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (- (+ 2.37 (* x 8.13008))) (- (* y 8.13008) 3.15)) (- 2.85 (* y 8.13008))) (+ 1.72 (* x 8.13008))) (fmin (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 2.85) 2) (pow (+ 1.945 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 2.85) 2) (pow (+ 1.945 (* x 8.13008)) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 2.85) 2) (pow (+ 2.195 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 2.85) 2) (pow (+ 2.195 (* x 8.13008)) 2))) 0.175)))) (fmax (fmax (- 0.195 (* y 0.813008)) (- (+ 2.42975 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 2.67975 (* x 4.47154))))))) (fmin (fmin (fmax (fmax (- (+ 2.67975 (* x 4.47154)) (* y 2.64228)) (- (* y 1.82927) (+ 2.42975 (* x 4.47154)))) (- (* y 0.813008) 0.195)) (fmax (fmax (- (+ 2.67975 (* x 4.47154)) (* y 2.64228)) (- (* y 0.813008) 0.25)) (- (* y 1.82927) (+ 2.48475 (* x 4.47154))))) (fmin (fmax (fmax (- (* y 2.64228) (+ 2.67975 (* x 4.47154))) (- (+ 2.48475 (* x 4.47154)) (* y 1.82927))) (- 0.25 (* y 0.813008))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- (+ 2.42975 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 2.73475 (* x 4.47154)))) (fmax (fmax (- (* y 0.813008) 0.25) (- (* y 1.82927) (+ 2.42975 (* x 4.47154)))) (- (+ 2.73475 (* x 4.47154)) (* y 2.64228))))))) (fmin (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.305) (- (* y 1.82927) (+ 2.48475 (* x 4.47154)))) (- (+ 2.73475 (* x 4.47154)) (* y 2.64228))) (fmax (fmax (- 0.305 (* y 0.813008)) (- (+ 2.48475 (* x 4.47154)) (* y 1.82927))) (- (* y 2.64228) (+ 2.73475 (* x 4.47154))))) (fmin (fmax (fmax (- 0.25 (* y 0.813008)) (- (+ (+ 1.35975 (* y 1.82927)) (* x 4.47154)))) (+ (+ 1.05475 (* y 2.64228)) (* x 4.47154))) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (+ (+ 1.35975 (* y 1.82927)) (* x 4.47154))) (- (+ (+ 1.05475 (* y 2.64228)) (* x 4.47154)))) (fmax (fmax (- (* y 0.813008) 0.305) (- (+ (+ 1.05475 (* y 2.64228)) (* x 4.47154)))) (+ (+ 1.30475 (* y 1.82927)) (* x 4.47154)))))) (fmin (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (+ (+ 1.05475 (* y 2.64228)) (* x 4.47154))) (- (+ (+ 1.30475 (* y 1.82927)) (* x 4.47154)))) (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- (+ 4.725 (* x 8.13008)))) (- (+ 0.0749998 (* y 8.13008)))) (+ 4.625 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 4.45 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 4.45 (* x 8.13008)) 2))) 0.275)) (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (+ 4.825 (* x 8.13008))) (- (+ 4.925 (* x 8.13008)))) (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- 0.575 (* y 8.13008))) (+ 5.275 (* x 8.13008))) (- (+ 5.375 (* x 8.13008))))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.575)) (+ 4.825 (* x 8.13008))) (- (+ 5.375 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 5.1 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 5.1 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.3 (* y 8.13008))) (- (+ 6.275 (* x 8.13008)))) (+ 6.175 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- 0.75 (* y 8.13008))) (- (+ 6.45 (* x 8.13008)))) (+ 6.275 (* x 8.13008))) (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (+ 6.45 (* x 8.13008)))) (+ 6.275 (* x 8.13008))) (- (* y 8.13008) 0.4)) (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.3 (* y 8.13008))) (+ 6.45 (* x 8.13008))) (- (+ 7.175 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (+ 6.45 (* x 8.13008))) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (- (+ 6.45 (* x 8.13008))) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (+ 6.8 (* x 8.13008))) (- (* y 8.13008) 0.625)) (- (+ 6.9 (* x 8.13008)))) (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (+ 7.25 (* x 8.13008))) (- (+ 7.35 (* x 8.13008))))) (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.575)) (+ 1.025 (* x 8.13008))) (- (+ 1.125 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.3 (* y 8.13008))) (+ 1.475 (* x 8.13008))) (- (+ 1.575 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- 0.575 (* y 8.13008))) (+ 1.025 (* x 8.13008))) (- (+ 1.575 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 1.3 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 1.3 (* x 8.13008)) 2))) 0.275)))))) (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.3) (- 0.55 (* y 8.13008))) (+ 1.825 (* x 8.13008))) (- (+ 1.925 (* x 8.13008)))) (fmax (fmax (fmax (- (* y 8.13008) 0.95) (- 0.85 (* y 8.13008))) (+ 1.675 (* x 8.13008))) (- (+ 2.075 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.55) (- 0.3 (* y 8.13008))) (+ 1.675 (* x 8.13008))) (- (+ 2.075 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (+ 1.675 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.55) 2) (pow (+ 1.675 (* x 8.13008)) 2))) 0.25)) (fmin (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.625)) (+ 2.875 (* x 8.13008))) (- (+ 2.975 (* x 8.13008)))) (fmax (fmax (fmax (- 0.3 (* y 8.13008)) (- (* y 8.13008) 0.85)) (+ 3.325 (* x 8.13008))) (- (+ 3.425 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 0.85) (- 0.625 (* y 8.13008))) (+ 2.875 (* x 8.13008))) (- (+ 3.425 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 3.15 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 3.15 (* x 8.13008)) 2))) 0.275)) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 3.8 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 3.8 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 1.95)) (- (* x 8.13008) 5.958)) (- 5.858 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- 1.725 (* y 8.13008)) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 6.133) 2) (pow (- (* y 8.13008) 1.675) 2))))) (- (sqrt (+ (pow (- (* x 8.13008) 6.133) 2) (pow (- (* y 8.13008) 1.675) 2))) 0.275)) (- (* x 8.13008) 6.408)) (- (* y 8.13008) 1.95)) (- 5.858 (* x 8.13008))) (fmin (fmax (fmax (fmax (- (* x 8.13008) 5.733) (- 5.633 (* x 8.13008))) (- 1.4 (* y 8.13008))) (- (* y 8.13008) 1.95)) (- (sqrt (+ (pow (- (* y 8.13008) 2.1) 2) (pow (- (* x 8.13008) 5.683) 2))) 0.075))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.75) (- (* x 8.13008) 5.508)) (- 5.408 (* x 8.13008))) (- 1.4 (* y 8.13008))) (fmax (fmax (fmax (- (* y 8.13008) 1.75) (- (* x 8.13008) 5.258)) (- 5.158 (* x 8.13008))) (- 1.4 (* y 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.925) (- (* x 8.13008) 5.008)) (- 4.908 (* x 8.13008))) (- 1.4 (* y 8.13008))) (fmin (fmax (fmax (fmax (fmax (- 4.908 (* x 8.13008)) (- (* y 8.13008) 2.05)) (- 1.75 (* y 8.13008))) (- (* x 8.13008) 5.558)) (fmin (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 1.75) 2) (pow (- (* x 8.13008) 5.333) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 1.75) 2) (pow (- (* x 8.13008) 5.333) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 1.75) 2) (pow (- (* x 8.13008) 5.083) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 1.75) 2) (pow (- (* x 8.13008) 5.083) 2))) 0.175)))) (fmax (fmax (fmax (fmax (fmax (+ 6.8 (* x 8.13008)) (- (* y 8.13008) 0.85)) (- 0.625 (* y 8.13008))) (- (+ 7.35 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 7.075 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 0.575) 2) (pow (+ 7.075 (* x 8.13008)) 2))) 0.275))))) (fmin (fmin (fmax (- (sqrt (+ (pow (+ 7.725 (* x 8.13008)) 2) (pow (- (* y 8.13008) 0.575) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 7.45 (* x 8.13008)) (- (+ 7.95 (* x 8.13008)))) (- (* y 8.13008) 0.615)) (- 0.525 (* y 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 7.725 (* x 8.13008)) 2) (pow (- (* y 8.13008) 0.575) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 0.37375) (- (+ 2.08 (* x 2.23577)) (* y 1.21951))) (- (+ (+ 1.885 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (- 0.37375 (* y 5.28455)) (+ (+ 1.885 (* x 2.23577)) (* y 4.06504))) (- (* y 1.21951) (+ 2.08 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (+ 7.725 (* x 8.13008)) 2) (pow (- (* y 8.13008) 0.575) 2))))))) (fmax (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 6.783) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.715) (- 1.625 (* y 8.13008))) (- (* x 8.13008) 7.058)) (- 6.558 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 6.783) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 1.08875) (- (* x 2.23577) (+ (* y 1.21951) 1.7447))) (- 2.6547 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 2.6547) (- (+ (* y 1.21951) 1.7447) (* x 2.23577))) (- 1.08875 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 6.783) 2)))))))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.725) (- 1.4 (* y 8.13008))) (- (* x 8.13008) 6.408)) (- 6.308 (* x 8.13008))) (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 2.775) 2))) 0.275) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 2.775) 2))))) (fmax (fmax (- 0.14 (* y 0.813008)) (- (* x 4.47154) (+ 0.8325 (* y 2.03252)))) (- (+ 0.6375 (* y 2.84553)) (* x 4.47154))))))) (fmin (fmin (fmin (fmax (fmax (- (* x 4.47154) (+ 0.6375 (* y 2.84553))) (- (+ 0.8325 (* y 2.03252)) (* x 4.47154))) (- (* y 0.813008) 0.14)) (fmax (fmax (- (* x 4.47154) (+ 0.6375 (* y 2.84553))) (- (* y 0.813008) 0.195)) (- (+ 0.7775 (* y 2.03252)) (* x 4.47154)))) (fmin (fmax (fmax (- (+ 0.6375 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ 0.7775 (* y 2.03252)))) (- 0.195 (* y 0.813008))) (fmin (fmax (fmax (- 0.14 (* y 0.813008)) (- 1.4775 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 1.6725)) (fmax (fmax (- (* y 0.813008) 0.14) (- 1.6725 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.4775))))) (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.195) (- 1.6725 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.5325)) (fmin (fmax (fmax (- 0.195 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 1.6725)) (- 1.5325 (+ (* y 2.03252) (* x 4.47154)))) (fmax (fmax (- 0.14 (* y 0.813008)) (- (* x 4.47154) (+ 0.5575 (* y 2.03252)))) (- (+ 0.3625 (* y 2.84553)) (* x 4.47154))))) (fmin (fmax (fmax (- (* y 0.813008) 0.14) (- (* x 4.47154) (+ 0.3625 (* y 2.84553)))) (- (+ 0.5575 (* y 2.03252)) (* x 4.47154))) (fmin (fmax (fmax (- (* y 0.813008) 0.195) (- (* x 4.47154) (+ 0.3625 (* y 2.84553)))) (- (+ 0.5025 (* y 2.03252)) (* x 4.47154))) (fmax (fmax (- 0.195 (* y 0.813008)) (- (+ 0.3625 (* y 2.84553)) (* x 4.47154))) (- (* x 4.47154) (+ 0.5025 (* y 2.03252))))))))) (fmin (fmin (fmin (fmin (fmax (fmax (- 0.14 (* y 0.813008)) (- 1.2025 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 1.3975)) (fmax (fmax (- (* y 0.813008) 0.14) (- 1.3975 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.2025))) (fmin (fmax (fmax (- (* y 0.813008) 0.195) (- 1.3975 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 1.2575)) (fmin (fmax (fmax (- 0.195 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 1.3975)) (- 1.2575 (+ (* y 2.03252) (* x 4.47154)))) (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 0.224999) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (* x 8.13008) 0.224999) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (fmin (fmin (fmin (fmax (fmax (- (* y 4.06504) 1.2) (- (* x 4.06504) 2.104)) (- 3.054 (* (+ x y) 4.06504))) (fmax (fmax (- (* (+ x y) 4.06504) 3.054) (- 2.104 (* x 4.06504))) (- 1.2 (* y 4.06504)))) (fmin (fmax (fmax (- (* x 8.13008) (+ (* y 0.813008) 3.968)) (- 1.8858 (+ (* y 2.60163) (* x 2.84553)))) (- (+ 1.7272 (* y 3.41463)) (* x 5.28455))) (fmax (fmax (- (* x 5.28455) (+ 1.7272 (* y 3.41463))) (- (+ (* y 2.60163) (* x 2.84553)) 1.8858)) (- (+ (* y 0.813008) 3.968) (* x 8.13008))))) (fmin (fmin (fmax (fmax (- 0.351 (* y 2.19512)) (- 1.2978 (* x 2.84553))) (- (+ (* y 2.19512) (* x 2.84553)) 1.7433)) (fmax (fmax (- 1.7433 (+ (* y 2.19512) (* x 2.84553))) (- (* x 2.84553) 1.2978)) (- (* y 2.19512) 0.351))) (fmin (fmax (fmax (- (* y 3.25203) 0.96) (- (* x 4.47154) (+ 1.2994 (* y 3.25203)))) (- 2.0394 (* x 4.47154))) (fmax (fmax (- (* x 4.47154) 2.0394) (- (+ 1.2994 (* y 3.25203)) (* x 4.47154))) (- 0.96 (* y 3.25203)))))))) (- (* y 8.13008) 2.4)) (- (* x 8.13008) 4.108)) (- 3.608 (* x 8.13008))) (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 1.95)) (- (* x 8.13008) 3.25)) (- 3.15 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 1.95)) (- (* x 11.6144) 5.05)) (- 4.5 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 1.4) 2) (pow (- (* x 14.518) 6.3125) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 1.4) 2) (pow (- (* x 11.6144) 5.05) 2))) 0.55)) (fmin (fmax (fmax (fmax (- (* y 8.13008) 1.95) (- (+ 2.55 (* x 8.13008)))) (+ 2.375 (* x 8.13008))) (- 1.85 (* y 8.13008))) (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (+ 2.55 (* x 8.13008)))) (- (* y 8.13008) 1.5)) (+ 2.375 (* x 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 2.4)) (+ 2.55 (* x 8.13008))) (- (+ 3.275 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (+ 2.55 (* x 8.13008))) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (- (+ 2.55 (* x 8.13008))) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 2.4) (- 2.3 (* y 8.13008))) (+ 3.6 (* x 8.13008))) (- (+ 4.1 (* x 8.13008))))) (fmin (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 1.5)) (+ 3.6 (* x 8.13008))) (- (+ 4.1 (* x 8.13008)))) (fmin (fmax (fmax (fmax (+ 3.8 (* x 8.13008)) (- 1.4 (* y 8.13008))) (- (* y 8.13008) 2.4)) (- (+ 3.9 (* x 8.13008)))) (fmax (fmax (- 0.14 (* y 0.813008)) (- (+ 3.1825 (* x 4.47154)) (* y 2.03252))) (- (* y 2.84553) (+ 3.3775 (* x 4.47154))))))) (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.14) (- (+ 3.3775 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.1825 (* x 4.47154)))) (fmin (fmax (fmax (- (* y 0.813008) 0.195) (- (+ 3.3775 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.2375 (* x 4.47154)))) (fmax (fmax (- 0.195 (* y 0.813008)) (- (* y 2.84553) (+ 3.3775 (* x 4.47154)))) (- (+ 3.2375 (* x 4.47154)) (* y 2.03252))))) (fmin (fmax (fmax (- 0.14 (* y 0.813008)) (- (+ (+ (* y 2.03252) 2.5375) (* x 4.47154)))) (+ (+ 2.3425 (* y 2.84553)) (* x 4.47154))) (fmin (fmax (fmax (- (* y 0.813008) 0.14) (+ (+ (* y 2.03252) 2.5375) (* x 4.47154))) (- (+ (+ 2.3425 (* y 2.84553)) (* x 4.47154)))) (fmax (fmax (- (* y 0.813008) 0.195) (- (+ (+ 2.3425 (* y 2.84553)) (* x 4.47154)))) (+ (+ (* y 2.03252) 2.4825) (* x 4.47154))))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (- 0.195 (* y 0.813008)) (+ (+ 2.3425 (* y 2.84553)) (* x 4.47154))) (- (+ (+ (* y 2.03252) 2.4825) (* x 4.47154)))) (fmax (fmax (- 0.14 (* y 0.813008)) (- (+ 3.4575 (* x 4.47154)) (* y 2.03252))) (- (* y 2.84553) (+ 3.6525 (* x 4.47154))))) (fmin (fmax (fmax (- (* y 0.813008) 0.14) (- (+ 3.6525 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.4575 (* x 4.47154)))) (fmin (fmax (fmax (- (* y 0.813008) 0.195) (- (+ 3.6525 (* x 4.47154)) (* y 2.84553))) (- (* y 2.03252) (+ 3.5125 (* x 4.47154)))) (fmax (fmax (- 0.195 (* y 0.813008)) (- (* y 2.84553) (+ 3.6525 (* x 4.47154)))) (- (+ 3.5125 (* x 4.47154)) (* y 2.03252)))))) (fmin (fmin (fmax (fmax (fmax (- 1.65 (* y 8.13008)) (+ 0.300001 (* x 8.13008))) (- (* y 8.13008) 2.4)) (- (+ 0.400001 (* x 8.13008)))) (fmax (fmax (fmax (+ 0.150001 (* x 8.13008)) (- (* y 8.13008) 2.05)) (- 1.95 (* y 8.13008))) (- (+ 0.550001 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (+ 0.150001 (* x 8.13008)) (- 1.4 (* y 8.13008))) (- (+ 0.550001 (* x 8.13008)))) (- (* y 8.13008) 1.65)) (- 0.15 (sqrt (+ (pow (+ 0.150001 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.65) 2))))) (- (sqrt (+ (pow (+ 0.150001 (* x 8.13008)) 2) (pow (- (* y 8.13008) 1.65) 2))) 0.25)) (fmin (fmax (fmax (fmax (fmax (fmax (- (fmin (fmax (fmax (- (+ 0.65875 (* x 2.84553)) (* y 0.813008)) (- 0.281875 (+ (* x 1.82927) (* y 4.06504)))) (- (* y 4.87805) (+ (* x 1.01626) 1.13813))) (fmax (fmax (- (+ (* x 1.01626) 1.13813) (* y 4.87805)) (- (+ (* x 1.82927) (* y 4.06504)) 0.281875)) (- (* y 0.813008) (+ 0.65875 (* x 2.84553)))))) (- (* y 8.13008) 1.475)) (- 1.25 (* y 8.13008))) (+ 1.375 (* x 8.13008))) (- (+ 1.525 (* x 8.13008)))) (- (sqrt (+ (pow (- (* y 2.71003) 0.491667) 2) (pow (+ 1.45 (* x 8.13008)) 2))) 0.075)) (- (sqrt (+ (pow (- (* y 8.13008) 1.475) 2) (pow (+ 1.425 (* x 8.13008)) 2))) 0.075))))) (fmin (fmin (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (+ 1.9 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 1.675) 2) (pow (+ 1.9 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- 1.4 (* y 8.13008)) (- (* y 8.13008) 2.4)) (- (+ 2.375 (* x 8.13008)))) (+ 2.275 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- (sqrt (+ (pow (- (* x 8.13008) 1.951) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (- (* x 8.13008) 2.226)) (- 1.676 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 1.951) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (* y 8.13008) 5.25)) (- 5.025 (* y 8.13008))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 5.05) (- (* x 8.13008) 1.556)) (- 1.456 (* x 8.13008))) (- 4.7 (* y 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (- (+ 0.146856 (* x 8.13008)) (* y 2.32288)) 2) (pow (- (* y 8.13008) 4.975) 2)))) (- (sqrt (+ (pow (- (+ 0.146856 (* x 8.13008)) (* y 2.32288)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (fmin (fmax (fmax (- (+ 0.44765 (* x 2.84553)) (* y 0.813008)) (- 2.27973 (+ (* x 1.82927) (* y 4.06504)))) (- (* y 4.87805) (+ (* x 1.01626) 2.92488))) (fmax (fmax (- (+ (* x 1.01626) 2.92488) (* y 4.87805)) (- (+ (* x 1.82927) (* y 4.06504)) 2.27973)) (- (* y 0.813008) (+ 0.44765 (* x 2.84553)))))) (- (* y 8.13008) 4.775)) (- 4.55 (* y 8.13008))) (- (* x 8.13008) 0.171)) (- 0.0209999 (* x 8.13008))) (- (sqrt (+ (pow (- (* y 2.71003) 1.59167) 2) (pow (- (* x 8.13008) 0.0959997) 2))) 0.075)) (- (sqrt (+ (pow (- (* y 8.13008) 4.775) 2) (pow (- (* x 8.13008) 0.121) 2))) 0.075)) (fmin (fmax (fmax (- 0.525 (* y 0.813008)) (- (+ (* y 2.84553) (* x 4.47154)) 4.12055)) (- 3.65055 (+ (* y 2.03252) (* x 4.47154)))) (fmin (fmax (fmax (- (* x 4.47154) (+ 1.02555 (* y 2.03252))) (- (+ 0.500551 (* y 2.84553)) (* x 4.47154))) (- 0.47 (* y 0.813008))) (fmax (fmax (- (* x 4.47154) (+ 0.500551 (* y 2.84553))) (- (+ 1.02555 (* y 2.03252)) (* x 4.47154))) (- (* y 0.813008) 0.47))))))) (fmin (fmin (fmin (fmin (fmax (fmax (- (* x 4.47154) (+ 0.500551 (* y 2.84553))) (- (+ 0.970551 (* y 2.03252)) (* x 4.47154))) (- (* y 0.813008) 0.525)) (fmax (fmax (- (+ 0.500551 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ 0.970552 (* y 2.03252)))) (- 0.525 (* y 0.813008)))) (fmin (fmax (fmax (- 3.32055 (+ (* y 2.03252) (* x 4.47154))) (- (+ (* y 2.84553) (* x 4.47154)) 3.84555)) (- 0.47 (* y 0.813008))) (fmin (fmax (fmax (- 3.84555 (+ (* y 2.84553) (* x 4.47154))) (- (+ (* y 2.03252) (* x 4.47154)) 3.32055)) (- (* y 0.813008) 0.47)) (fmax (fmax (- 3.84555 (+ (* y 2.84553) (* x 4.47154))) (- (+ (* y 2.03252) (* x 4.47154)) 3.37555)) (- (* y 0.813008) 0.525))))) (fmin (fmin (fmax (fmax (- (+ (* y 2.84553) (* x 4.47154)) 3.84555) (- 3.37555 (+ (* y 2.03252) (* x 4.47154)))) (- 0.525 (* y 0.813008))) (fmax (fmax (fmax (- 2.751 (* x 8.13008)) (- (* x 8.13008) 2.851)) (- (* y 8.13008) 5.7)) (- 4.7 (* y 8.13008)))) (fmin (fmax (fmax (fmax (- 5.15 (* y 8.13008)) (- 2.576 (* x 8.13008))) (- (* x 8.13008) 2.751)) (- (* y 8.13008) 5.25)) (fmin (fmax (fmax (fmax (- 2.576 (* x 8.13008)) (- (* x 8.13008) 2.751)) (- (* y 8.13008) 4.8)) (- 4.7 (* y 8.13008))) (fmax (fmax (fmax (fmax (fmax (- 1.851 (* x 8.13008)) (- (* x 8.13008) 2.576)) (- 0.175 (sqrt (+ (pow (- 2.576 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (sqrt (+ (pow (- 2.576 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (- (* y 8.13008) 5.7)) (- 4.7 (* y 8.13008))))))) (fmin (fmin (fmin (fmax (fmax (fmax (- (* x 8.13008) 2.226) (- 2.126 (* x 8.13008))) (- (* y 8.13008) 5.025)) (- 4.7 (* y 8.13008))) (fmax (fmax (fmax (- (* x 8.13008) 1.776) (- 1.676 (* x 8.13008))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25))) (fmin (fmax (fmax (fmax (- 4.6 (* y 8.13008)) (+ 1.862 (* x 8.13008))) (- (+ 1.962 (* x 8.13008)))) (- (* y 8.13008) 5.25)) (fmin (- (sqrt (+ (pow (- (* y 8.13008) 5.4) 2) (pow (+ 1.912 (* x 8.13008)) 2))) 0.075) (fmax (fmax (fmax (+ 2.662 (* x 8.13008)) (- (+ 2.762 (* x 8.13008)))) (- (* y 8.13008) 5.7)) (- 4.7 (* y 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- 5.15 (* y 8.13008)) (+ 2.487 (* x 8.13008))) (- (+ 2.662 (* x 8.13008)))) (- (* y 8.13008) 5.25)) (fmin (fmax (fmax (fmax (- (* y 8.13008) 4.8) (+ 2.487 (* x 8.13008))) (- (+ 2.662 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (fmax (fmax (fmax (fmax (fmax (+ 1.762 (* x 8.13008)) (- (+ 2.487 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 2.487 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (sqrt (+ (pow (+ 2.487 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (- (* y 8.13008) 5.7)) (- 4.7 (* y 8.13008))))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 5.05) (+ 2.882 (* x 8.13008))) (- (+ 2.982 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (+ 4.58486 (* x 8.13008)) (* y 2.32288)) 2) (pow (- (* y 8.13008) 4.975) 2)))) (- (sqrt (+ (pow (- (+ 4.58486 (* x 8.13008)) (* y 2.32288)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (fmax (- (sqrt (+ (pow (+ 0.354001 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 0.0790005 (* x 8.13008)) (- (+ 0.579001 (* x 8.13008)))) (- (* y 8.13008) 5.015)) (- 4.925 (* y 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 0.354001 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (- (fmin (fmax (fmax (- (+ 0.712975 (* x 2.23577)) (* y 1.21951)) (- 2.34202 (+ (* x 2.23577) (* y 4.06504)))) (- (* y 5.28455) 3.23375)) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 2.34202) (- (* y 1.21951) (+ 0.712975 (* x 2.23577)))) (- 3.23375 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (+ 0.354001 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (+ 0.987 (* x 8.13008)) (- (+ 1.087 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25)) (fmax (fmax (fmax (fmax (fmax (+ 1.00286 (* x 11.6144)) (- (+ 1.55286 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 4.7) 2) (pow (+ 1.25357 (* x 14.518)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 4.7) 2) (pow (+ 1.00286 (* x 11.6144)) 2))) 0.55)) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25))) (fmin (fmax (fmax (fmax (+ 1.187 (* x 8.13008)) (- (+ 1.287 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25)) (fmin (fmax (fmax (fmax (+ 1.637 (* x 8.13008)) (- (+ 1.737 (* x 8.13008)))) (- 4.975 (* y 8.13008))) (- (* y 8.13008) 5.25)) (fmax (fmax (fmax (fmax (fmax (+ 1.187 (* x 8.13008)) (- (+ 1.737 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 1.462 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (sqrt (+ (pow (+ 1.462 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 4.975))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 4.325 (* y 8.13008)) (+ 1.587 (* x 8.13008))) (- (+ 2.137 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 4.6) 2) (pow (+ 2.137 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 4.6) 2) (pow (+ 2.137 (* x 8.13008)) 2))) 0.275)) (- (* y 8.13008) 4.6)) (fmax (fmax (fmax (- (* y 8.13008) 5.9) (- 5.8 (* y 8.13008))) (- (* x 8.13008) 6.5305)) (- 6.0305 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* x 8.13008) 6.3305)) (- 6.2305 (* x 8.13008))) (- (* y 8.13008) 6.8)) (fmin (fmax (fmax (- (fmin (fmax (fmax (- (* y 2.23577) (+ 0.263484 (* x 2.27642))) (- (* x 4.5122) 2.94178)) (- 3.05264 (* (+ x y) 2.23577))) (fmax (fmax (- (* (+ x y) 2.23577) 3.05264) (- 2.94178 (* x 4.5122))) (- (+ 0.263484 (* x 2.27642)) (* y 2.23577))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 5.0255) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 5.0255) 2))) 0.275)) (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.35)) (- (* x 8.13008) 4.6255)) (- 4.5255 (* x 8.13008))))))) (fmin (fmin (fmin (- (sqrt (+ (pow (- (* y 8.13008) 6.5) 2) (pow (- (* x 8.13008) 4.5755) 2))) 0.075) (fmax (fmax (fmax (- (* y 8.13008) 6.35) (- 5.7 (* y 8.13008))) (- (* x 8.13008) 4.4005)) (- 4.3005 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 4.1255) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 4.1255) 2))) 0.275)) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 4.4005) (- 5.425 (* y 8.13008))) (- 3.8505 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 4.1255) 2) (pow (- (* y 8.13008) 5.7) 2))))) (- (sqrt (+ (pow (- (* x 8.13008) 4.1255) 2) (pow (- (* y 8.13008) 5.7) 2))) 0.275)) (- (* y 8.13008) 5.7)) (fmax (- (sqrt (+ (pow (+ 4.517 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 4.242 (* x 8.13008)) (- (+ 4.742 (* x 8.13008)))) (- (* y 8.13008) 5.015)) (- 4.925 (* y 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 4.517 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (- (fmin (fmax (fmax (- (+ 1.8578 (* x 2.23577)) (* y 1.21951)) (- 1.1972 (+ (* x 2.23577) (* y 4.06504)))) (- (* y 5.28455) 3.23375)) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 1.1972) (- (* y 1.21951) (+ 1.8578 (* x 2.23577)))) (- 3.23375 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (+ 4.517 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2)))))))))) (fmin (fmin (fmax (fmax (fmax (+ 5.15 (* x 8.13008)) (- (+ 5.25 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25)) (fmin (fmax (fmax (fmax (fmax (fmax (+ 6.95 (* x 11.6144)) (- (+ 7.5 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 4.7) 2) (pow (+ 8.6875 (* x 14.518)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 4.7) 2) (pow (+ 6.95 (* x 11.6144)) 2))) 0.55)) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 5.25)) (fmax (- (sqrt (+ (pow (+ 5.625 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (fmin (fmax (fmax (fmax (+ 5.35 (* x 8.13008)) (- (+ 5.85 (* x 8.13008)))) (- (* y 8.13008) 5.015)) (- 4.925 (* y 8.13008))) (fmax (fmax (- (sqrt (+ (pow (+ 5.625 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275) (- (fmin (fmax (fmax (- (+ 2.1625 (* x 2.23577)) (* y 1.21951)) (- 0.8925 (+ (* x 2.23577) (* y 4.06504)))) (- (* y 5.28455) 3.23375)) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 0.8925) (- (* y 1.21951) (+ 2.1625 (* x 2.23577)))) (- 3.23375 (* y 5.28455)))))) (- 0.175 (sqrt (+ (pow (+ 5.625 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))))))) (fmin (fmax (fmax (fmax (+ 6.05 (* x 8.13008)) (- (+ 6.15 (* x 8.13008)))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 4.975)) (fmin (fmax (fmax (fmax (+ 6.5 (* x 8.13008)) (- (+ 6.6 (* x 8.13008)))) (- (* y 8.13008) 5.7)) (- 4.7 (* y 8.13008))) (fmax (fmax (fmax (fmax (fmax (- (+ 6.6 (* x 8.13008))) (+ 6.05 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (sqrt (+ (pow (+ 6.325 (* x 8.13008)) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (- 4.975 (* y 8.13008))) (- (* y 8.13008) 5.25))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.8) (- 6.7 (* y 8.13008))) (- (* x 8.13008) 6.5305)) (- 6.0305 (* x 8.13008))) (fmax (fmax (fmax (- (* y 8.13008) 6.35) (- 5.7 (* y 8.13008))) (- (* x 8.13008) 0.9705)) (- 0.8705 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 0.695499) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 0.695499) 2))) 0.275)) (fmin (fmax (fmax (fmax (fmax (fmax (- 5.425 (* y 8.13008)) (- (* x 8.13008) 0.9705)) (- 0.4205 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 5.7) 2) (pow (- (* x 8.13008) 0.695499) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 5.7) 2) (pow (- (* x 8.13008) 0.695499) 2))) 0.275)) (- (* y 8.13008) 5.7)) (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.35)) (- (* x 8.13008) 0.320499)) (- 0.220499 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.35) (+ 0.129501 (* x 8.13008))) (- (+ 0.229501 (* x 8.13008)))) (- 6.075 (* y 8.13008))) (fmax (fmax (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.075)) (- (* x 8.13008) 0.320499)) (- (+ 0.229501 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 0.0454988) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 0.0454988) 2))) 0.275))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 0.604501 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 0.604501 (* x 8.13008)) 2))) 0.275)) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.35)) (+ 1.2375 (* x 8.13008))) (- (+ 1.3375 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.35)) (+ 1.36071 (* x 11.6144))) (- (+ 1.91072 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 5.8) 2) (pow (+ 1.70089 (* x 14.518)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 5.8) 2) (pow (+ 1.36071 (* x 11.6144)) 2))) 0.55)))))) (fmin (fmin (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.15)) (- (* x 8.13008) 3.7305)) (- 3.6305 (* x 8.13008))) (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) (+ 1.71336 (* y 2.32288))) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) (+ 1.71336 (* y 2.32288))) 2))) 0.275))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.15)) (- (* x 8.13008) 3.0705)) (- 2.9705 (* x 8.13008))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.15)) (- (* x 8.13008) 2.8205)) (- 2.7205 (* x 8.13008))) (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.325)) (- (* x 8.13008) 2.5705)) (- 2.4705 (* x 8.13008)))))) (fmin (fmin (fmax (fmax (fmax (fmax (- 2.4705 (* x 8.13008)) (- (* y 8.13008) 6.45)) (- 6.15 (* y 8.13008))) (- (* x 8.13008) 3.1205)) (fmin (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 6.15) 2) (pow (- (* x 8.13008) 2.8955) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.15) 2) (pow (- (* x 8.13008) 2.8955) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 6.15) 2) (pow (- (* x 8.13008) 2.6455) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.15) 2) (pow (- (* x 8.13008) 2.6455) 2))) 0.175)))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.075)) (- (* x 8.13008) 1.6205)) (- 1.5205 (* x 8.13008))) (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.8)) (- (* x 8.13008) 1.1705)) (- 1.0705 (* x 8.13008))))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 6.35) (- (* x 8.13008) 1.6205)) (- 1.0705 (* x 8.13008))) (- 6.075 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 1.3455) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (- (* x 8.13008) 1.3455) 2))) 0.275)) (fmin (fmax (fmin (fmax (fmax (fmax (- 3.825 (* y 8.13008)) (- (* x 8.13008) 6.4085)) (- 5.9085 (* x 8.13008))) (- (* y 8.13008) 3.915)) (fmax (fmax (- (fmin (fmax (fmax (- (* y 5.28455) 2.51875) (- (* x 2.23577) (+ (* y 1.21951) 1.23609))) (- 3.57609 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 3.57609) (- (+ (* y 1.21951) 1.23609) (* x 2.23577))) (- 2.51875 (* y 5.28455))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 6.1335) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 6.1335) 2))) 0.275))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 6.1335) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 3.95) (- 3.6 (* y 8.13008))) (- (* x 8.13008) 5.7585)) (- 5.6585 (* x 8.13008)))))))))) (fmin (fmin (fmin (fmin (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 3.95) (- 3.6 (* y 8.13008))) (- (* x 8.13008) 5.5085)) (- 5.4085 (* x 8.13008))) (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.125)) (- (* x 8.13008) 5.2585)) (- 5.1585 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (- 5.1585 (* x 8.13008)) (- (* y 8.13008) 4.25)) (- 3.95 (* y 8.13008))) (- (* x 8.13008) 5.8085)) (fmin (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 3.95) 2) (pow (- (* x 8.13008) 5.5835) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 3.95) 2) (pow (- (* x 8.13008) 5.5835) 2))) 0.175)) (fmax (- 0.075 (sqrt (+ (pow (- (* y 8.13008) 3.95) 2) (pow (- (* x 8.13008) 5.3335) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 3.95) 2) (pow (- (* x 8.13008) 5.3335) 2))) 0.175)))) (fmin (fmax (fmax (- (* y 0.813008) 0.25) (+ (+ (* y 2.03252) 2.521) (* x 4.47154))) (- (+ (+ 2.216 (* y 2.84553)) (* x 4.47154)))) (fmax (fmax (- (* y 0.813008) 0.305) (- (+ (+ 2.216 (* y 2.84553)) (* x 4.47154)))) (+ (+ (* y 2.03252) 2.466) (* x 4.47154)))))) (fmin (fmin (fmax (fmax (- 0.305 (* y 0.813008)) (+ (+ 2.216 (* y 2.84553)) (* x 4.47154))) (- (+ (+ (* y 2.03252) 2.466) (* x 4.47154)))) (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 2.85)) (+ 6.09 (* x 8.13008))) (- (+ 6.19 (* x 8.13008))))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (+ 7.16429 (* x 8.13008)) (* y 2.32288)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 2.775) 2) (pow (- (+ 7.16429 (* x 8.13008)) (* y 2.32288)) 2))) 0.275)) (fmin (fmax (- (sqrt (+ (pow (+ 7.025 (* x 8.13008)) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (fmin (fmax (fmax (fmax (- (* y 8.13008) 2.815) (- 2.725 (* y 8.13008))) (+ 6.75 (* x 8.13008))) (- (+ 7.25 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (+ 7.025 (* x 8.13008)) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 1.80375) (- (+ 2.2175 (* x 2.23577)) (* y 1.21951))) (- (+ (+ 0.5925 (* x 2.23577)) (* y 4.06504)))) (fmax (fmax (- 1.80375 (* y 5.28455)) (+ (+ 0.5925 (* x 2.23577)) (* y 4.06504))) (- (* y 1.21951) (+ 2.2175 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (+ 7.025 (* x 8.13008)) 2) (pow (- (* y 8.13008) 2.775) 2))))))) (fmax (fmax (fmax (+ 7.45 (* x 8.13008)) (- 2.5 (* y 8.13008))) (- (* y 8.13008) 2.775)) (- (+ 7.55 (* x 8.13008)))))))) (fmin (fmin (fmin (fmax (fmax (fmax (- 2.5 (* y 8.13008)) (- (* y 8.13008) 3.5)) (+ 7.9 (* x 8.13008))) (- (+ 8 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 7.45 (* x 8.13008)) (- (* y 8.13008) 3.05)) (- 2.775 (* y 8.13008))) (- (+ 8 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (+ 7.725 (* x 8.13008)) 2) (pow (- (* y 8.13008) 2.775) 2))))) (- (sqrt (+ (pow (+ 7.725 (* x 8.13008)) 2) (pow (- (* y 8.13008) 2.775) 2))) 0.275))) (fmin (fmax (fmax (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.6)) (- 2.121 (* x 8.13008))) (- (* x 8.13008) 2.846)) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- 2.846 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- 2.846 (* x 8.13008)) 2))) 0.275)) (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 2.221) 2))) 0.275) (fmin (fmax (fmax (fmax (- 3.825 (* y 8.13008)) (- (* y 8.13008) 3.915)) (- (* x 8.13008) 2.496)) (- 1.996 (* x 8.13008))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 2.221) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 2.51875) (- (* x 2.23577) (+ 0.16015 (* y 1.21951)))) (- 2.50015 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 2.51875 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 2.50015)) (- (+ 0.16015 (* y 1.21951)) (* x 2.23577)))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 2.221) 2))))))) (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* x 8.13008) 1.588)) (- 1.488 (* x 8.13008))) (- (* y 8.13008) 4.15))))) (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* x 11.6144) 2.67571)) (- 2.12571 (* x 11.6144))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 3.6) 2) (pow (- (* x 14.518) 3.34464) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.6) 2) (pow (- (* x 11.6144) 2.67571) 2))) 0.55)) (- (* y 8.13008) 4.15)) (fmin (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* x 8.13008) 1.363)) (- 1.263 (* x 8.13008))) (- (* y 8.13008) 4.15)) (- (sqrt (+ (pow (- (* y 8.13008) 4.3) 2) (pow (- (* x 8.13008) 1.313) 2))) 0.075))) (fmin (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* x 8.13008) 1.138)) (- 1.038 (* x 8.13008))) (- (* y 8.13008) 4.15)) (fmin (fmax (- (sqrt (+ (pow (- (* x 8.13008) 4.7835) 2) (pow (- (* y 8.13008) 3.875) 2))) 0.275) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 4.7835) 2) (pow (- (* y 8.13008) 3.875) 2))))) (fmax (- (fmin (fmax (fmax (fmax (- 3.825 (* y 8.13008)) (- (* y 8.13008) 3.9875)) (- 2.7765 (* x 5.42005))) (- (* x 5.42005) 2.939)) (- (sqrt (+ (pow (- 3.985 (* y 8.13008)) 2) (pow (- 1.84933 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- 3.9875 (* y 8.13008)) 2) (pow (- 2.7765 (* x 5.42005)) 2))) 0.1625))))))) (fmin (fmin (fmin (fmin (fmax (- (fmin (fmax (fmax (fmax (- (* y 8.13008) 3.925) (- 3.7625 (* y 8.13008))) (- (* x 5.42005) 2.7765)) (- 2.614 (* x 5.42005))) (- (sqrt (+ (pow (- (* y 8.13008) 3.765) 2) (pow (- (* x 3.61337) 1.85267) 2))) 0.0625))) (- (sqrt (+ (pow (- (* y 8.13008) 3.7625) 2) (pow (- (* x 5.42005) 2.7765) 2))) 0.1625)) (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.6)) (- 3.021 (* x 8.13008))) (- (* x 8.13008) 3.121))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 4.15) (- 4.05 (* y 8.13008))) (- 2.846 (* x 8.13008))) (- (* x 8.13008) 3.021)) (fmin (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- 2.846 (* x 8.13008))) (- (* x 8.13008) 3.021)) (- (* y 8.13008) 3.7)) (fmax (fmax (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.15)) (+ 1.12143 (* x 11.6144))) (- (+ 1.67143 (* x 11.6144)))) (- 0.45 (sqrt (+ (pow (- (* y 8.13008) 3.6) 2) (pow (+ 1.40179 (* x 14.518)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.6) 2) (pow (+ 1.12143 (* x 11.6144)) 2))) 0.55))))) (fmin (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 2.245 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (- 3.825 (* y 8.13008)) (- (* y 8.13008) 3.915)) (+ 1.97 (* x 8.13008))) (- (+ 2.47 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 2.245 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 2.51875) (- (+ 1.068 (* x 2.23577)) (* y 1.21951))) (- 1.272 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 2.51875 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 1.272)) (- (* y 1.21951) (+ 1.068 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 2.245 (* x 8.13008)) 2))))))) (fmax (fmax (- 0.36 (* y 0.813008)) (- (+ 2.4785 (* x 4.47154)) (* y 2.03252))) (- (* y 2.84553) (+ 2.8935 (* x 4.47154))))) (fmin (fmax (fmax (- (+ 2.8935 (* x 4.47154)) (* y 2.84553)) (- (* y 2.03252) (+ 2.4785 (* x 4.47154)))) (- (* y 0.813008) 0.36)) (fmin (fmax (fmax (- (+ 2.8935 (* x 4.47154)) (* y 2.84553)) (- (* y 0.813008) 0.415)) (- (* y 2.03252) (+ 2.5335 (* x 4.47154)))) (fmax (fmax (- (* y 2.84553) (+ 2.8935 (* x 4.47154))) (- (+ 2.5335 (* x 4.47154)) (* y 2.03252))) (- 0.415 (* y 0.813008))))))) (fmin (fmin (fmin (fmax (fmax (- 0.36 (* y 0.813008)) (- (+ (+ 0.7335 (* y 2.03252)) (* x 4.47154)))) (+ (+ 0.318501 (* y 2.84553)) (* x 4.47154))) (fmax (fmax (- (* y 0.813008) 0.36) (+ (+ 0.7335 (* y 2.03252)) (* x 4.47154))) (- (+ (+ 0.318501 (* y 2.84553)) (* x 4.47154))))) (fmin (fmax (fmax (- (* y 0.813008) 0.415) (- (+ (+ 0.318501 (* y 2.84553)) (* x 4.47154)))) (+ (+ 0.6785 (* y 2.03252)) (* x 4.47154))) (fmin (fmax (fmax (- 0.415 (* y 0.813008)) (+ (+ 0.318501 (* y 2.84553)) (* x 4.47154))) (- (+ (+ 0.6785 (* y 2.03252)) (* x 4.47154)))) (fmax (fmax (fmax (- (* y 8.13008) 3.95) (- 3.6 (* y 8.13008))) (+ 3.34 (* x 8.13008))) (- (+ 3.44 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (- 3.875 (* y 8.13008)) (- (* x 8.13008) 0.688)) (- 0.587999 (* x 8.13008))) (- (* y 8.13008) 4.15)) (fmin (fmax (fmax (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 3.875)) (- (* x 8.13008) 1.138)) (- 0.587999 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 0.862999) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 0.862999) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 4.15) (- 3.225 (* y 8.13008))) (- (* x 8.13008) 0.487999)) (- 0.387999 (* x 8.13008))))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 0.212998) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (* x 8.13008) 0.212998) 2))) 0.275)) (fmin (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 0.437001 (* x 8.13008)) 2))) 0.275) (fmin (fmax (fmax (fmax (- 3.825 (* y 8.13008)) (- (* y 8.13008) 3.915)) (+ 0.162001 (* x 8.13008))) (- (+ 0.662001 (* x 8.13008)))) (fmax (fmax (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 0.437001 (* x 8.13008)) 2))) 0.275) (- (fmin (fmax (fmax (- (* y 5.28455) 2.51875) (- (+ 0.5708 (* x 2.23577)) (* y 1.21951))) (- 1.7692 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- 2.51875 (* y 5.28455)) (- (+ (* x 2.23577) (* y 4.06504)) 1.7692)) (- (* y 1.21951) (+ 0.5708 (* x 2.23577))))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 0.437001 (* x 8.13008)) 2))))))) (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.15)) (+ 1.07 (* x 8.13008))) (- (+ 1.17 (* x 8.13008)))))))))) (fmin (fmin (fmin (fmin (fmin (fmax (fmin (fmax (fmax (- (fmin (fmax (fmax (- (* y 5.28455) 3.23375) (- (* x 2.23577) (+ 0.986526 (* y 1.21951)))) (- 4.04153 (+ (* x 2.23577) (* y 4.06504)))) (fmax (fmax (- (+ (* x 2.23577) (* y 4.06504)) 4.04153) (- (+ 0.986526 (* y 1.21951)) (* x 2.23577))) (- 3.23375 (* y 5.28455))))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 4.975) 2) (pow (- (* x 8.13008) 5.826) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 4.975) 2) (pow (- (* x 8.13008) 5.826) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 5.015) (- 4.925 (* y 8.13008))) (- (* x 8.13008) 6.101)) (- 5.601 (* x 8.13008)))) (- (sqrt (+ (pow (- (* y 8.13008) 4.975) 2) (pow (- (* x 8.13008) 5.826) 2))) 0.275)) (fmax (fmax (fmax (- (* x 8.13008) 5.401) (- 5.301 (* x 8.13008))) (- 4.7 (* y 8.13008))) (- (* y 8.13008) 4.975))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 5.7) (- (* x 8.13008) 4.951)) (- 4.851 (* x 8.13008))) (- 4.7 (* y 8.13008))) (fmin (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 5.401) (- 4.851 (* x 8.13008))) (- 4.975 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* x 8.13008) 5.126) 2) (pow (- (* y 8.13008) 4.975) 2))))) (- (sqrt (+ (pow (- (* x 8.13008) 5.126) 2) (pow (- (* y 8.13008) 4.975) 2))) 0.275)) (- (* y 8.13008) 5.25)) (fmax (fmax (- 0.47 (* y 0.813008)) (- (* x 4.47154) (+ 1.30055 (* y 2.03252)))) (- (+ 0.775551 (* y 2.84553)) (* x 4.47154)))))) (fmin (fmin (fmax (fmax (- (* x 4.47154) (+ 0.775551 (* y 2.84553))) (- (+ 1.30055 (* y 2.03252)) (* x 4.47154))) (- (* y 0.813008) 0.47)) (fmax (fmax (- (* x 4.47154) (+ 0.775551 (* y 2.84553))) (- (* y 0.813008) 0.525)) (- (+ 1.24555 (* y 2.03252)) (* x 4.47154)))) (fmin (fmax (fmax (- (+ 0.775551 (* y 2.84553)) (* x 4.47154)) (- (* x 4.47154) (+ 1.24555 (* y 2.03252)))) (- 0.525 (* y 0.813008))) (fmin (fmax (fmax (- 0.47 (* y 0.813008)) (- 3.59555 (+ (* y 2.03252) (* x 4.47154)))) (- (+ (* y 2.84553) (* x 4.47154)) 4.12055)) (fmax (fmax (- (* y 0.813008) 0.47) (- 4.12055 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 3.59555)))))) (fmin (fmin (fmin (fmax (fmax (- (* y 0.813008) 0.525) (- 4.12055 (+ (* y 2.84553) (* x 4.47154)))) (- (+ (* y 2.03252) (* x 4.47154)) 3.65055)) (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (+ 4.72857 (* x 8.13008)) (* y 2.32288)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (- (+ 4.72857 (* x 8.13008)) (* y 2.32288)) 2))) 0.275))) (fmin (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 3.875)) (+ 4.05 (* x 8.13008))) (- (+ 4.15 (* x 8.13008)))) (fmin (fmax (fmax (fmax (+ 4.5 (* x 8.13008)) (- (+ 4.6 (* x 8.13008)))) (- 3.6 (* y 8.13008))) (- (* y 8.13008) 4.6)) (fmax (fmax (fmax (fmax (fmax (- (+ 4.6 (* x 8.13008))) (- 3.875 (* y 8.13008))) (- (* y 8.13008) 4.15)) (+ 4.05 (* x 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 4.325 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 3.875) 2) (pow (+ 4.325 (* x 8.13008)) 2))) 0.275))))) (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 4.6) (- 4.5 (* y 8.13008))) (+ 5.4 (* x 8.13008))) (- (+ 5.9 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 3.7)) (+ 5.4 (* x 8.13008))) (- (+ 5.9 (* x 8.13008)))) (fmax (fmax (fmax (- 3.6 (* y 8.13008)) (- (* y 8.13008) 4.6)) (+ 5.6 (* x 8.13008))) (- (+ 5.7 (* x 8.13008)))))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 5.025) (- 4.7 (* y 8.13008))) (- (* x 8.13008) 6.75101)) (- 6.651 (* x 8.13008))) (fmin (fmax (fmax (fmax (- 4.7 (* y 8.13008)) (- (* y 8.13008) 5.25)) (- (* x 8.13008) 6.30101)) (- 6.201 (* x 8.13008))) (fmax (fmax (fmax (fmax (fmax (- (* x 8.13008) 6.75101) (- (* y 8.13008) 5.25)) (- 6.201 (* x 8.13008))) (- 5.025 (* y 8.13008))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 4.975) 2) (pow (- (* x 8.13008) 6.476) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 4.975) 2) (pow (- (* x 8.13008) 6.476) 2))) 0.275))))))) (fmin (fmin (fmin (fmin (fmax (fmax (fmax (fmax (fmax (- (* y 8.13008) 6.35) (- 6.075 (* y 8.13008))) (+ 3.025 (* x 8.13008))) (- (+ 3.575 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 3.3 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 3.3 (* x 8.13008)) 2))) 0.275)) (fmax (fmax (fmax (- (* y 8.13008) 6.8) (- 6.05 (* y 8.13008))) (+ 3.825 (* x 8.13008))) (- (+ 3.925 (* x 8.13008))))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.45) (- 6.35 (* y 8.13008))) (+ 3.675 (* x 8.13008))) (- (+ 4.075 (* x 8.13008)))) (fmin (fmax (fmax (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (+ 3.675 (* x 8.13008))) (- (+ 4.075 (* x 8.13008)))) (- (* y 8.13008) 6.05)) (- 0.15 (sqrt (+ (pow (- (* y 8.13008) 6.05) 2) (pow (+ 3.675 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.05) 2) (pow (+ 3.675 (* x 8.13008)) 2))) 0.25)) (fmax (fmax (fmax (- (* y 8.13008) 6.8) (- 6.05 (* y 8.13008))) (+ 5.025 (* x 8.13008))) (- (+ 5.125 (* x 8.13008))))))) (fmin (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.45) (- 6.35 (* y 8.13008))) (+ 4.875 (* x 8.13008))) (- (+ 5.275 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.05)) (+ 4.875 (* x 8.13008))) (- (+ 5.275 (* x 8.13008)))) (- 0.15 (sqrt (+ (pow (- (* y 8.13008) 6.05) 2) (pow (+ 4.875 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.05) 2) (pow (+ 4.875 (* x 8.13008)) 2))) 0.25))) (fmin (fmax (fmax (fmax (+ 5.375 (* x 8.13008)) (- 5.8 (* y 8.13008))) (- (* y 8.13008) 6.35)) (- (+ 5.475 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.35) (- 6.075 (* y 8.13008))) (+ 5.825 (* x 8.13008))) (- (+ 5.925 (* x 8.13008)))) (fmax (fmax (fmax (fmax (fmax (+ 5.375 (* x 8.13008)) (- 5.8 (* y 8.13008))) (- (* y 8.13008) 6.075)) (- (+ 5.925 (* x 8.13008)))) (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 5.65 (* x 8.13008)) 2))))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 5.65 (* x 8.13008)) 2))) 0.275)))))) (fmin (fmin (fmin (fmax (fmax (fmax (+ 6.5 (* x 8.13008)) (- (+ 6.6 (* x 8.13008)))) (- 5.8 (* y 8.13008))) (- (* y 8.13008) 6.8)) (fmax (fmax (fmax (- (+ 6.5 (* x 8.13008))) (- (* y 8.13008) 6.8)) (- 6.7 (* y 8.13008))) (+ 6.3 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- (+ 6.5 (* x 8.13008))) (- (* y 8.13008) 6.35)) (+ 6.3 (* x 8.13008))) (- 6.25 (* y 8.13008))) (fmin (fmax (fmax (fmax (- (+ 6.5 (* x 8.13008))) (- (* y 8.13008) 5.9)) (- 5.8 (* y 8.13008))) (+ 6.3 (* x 8.13008))) (fmax (- (fmin (fmax (fmax (fmax (- 6.025 (* y 8.13008)) (- (* y 8.13008) 6.1875)) (- (+ 1.5875 (* x 5.42005)))) (+ 1.425 (* x 5.42005))) (- (sqrt (+ (pow (- 6.185 (* y 8.13008)) 2) (pow (- (+ 1.06 (* x 3.61337))) 2))) 0.0625))) (- (sqrt (+ (pow (- 6.1875 (* y 8.13008)) 2) (pow (- (+ 1.5875 (* x 5.42005))) 2))) 0.1625))))) (fmin (fmin (fmax (- (fmin (fmax (fmax (fmax (- (* y 8.13008) 6.125) (- 5.9625 (* y 8.13008))) (+ 1.5875 (* x 5.42005))) (- (+ 1.75 (* x 5.42005)))) (- (sqrt (+ (pow (- (* y 8.13008) 5.965) 2) (pow (+ 1.05667 (* x 3.61337)) 2))) 0.0625))) (- (sqrt (+ (pow (- (* y 8.13008) 5.9625) 2) (pow (+ 1.5875 (* x 5.42005)) 2))) 0.1625)) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.35)) (+ 2.75 (* x 8.13008))) (- (+ 2.85 (* x 8.13008)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.5) 2) (pow (+ 2.8 (* x 8.13008)) 2))) 0.075))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.075)) (+ 3.025 (* x 8.13008))) (- (+ 3.125 (* x 8.13008)))) (fmin (fmax (fmax (fmax (- 5.8 (* y 8.13008)) (- (* y 8.13008) 6.8)) (+ 3.475 (* x 8.13008))) (- (+ 3.575 (* x 8.13008)))) (fmax (fmax (fmax (fmax (+ 5.6 (* x 8.13008)) (- 5.8 (* y 8.13008))) (- (* y 8.13008) 6.8)) (- (+ 6.3 (* x 8.13008)))) (fmin (fmax (- 0.175 (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 6.3 (* x 8.13008)) 2)))) (- (sqrt (+ (pow (- (* y 8.13008) 6.075) 2) (pow (+ 6.3 (* x 8.13008)) 2))) 0.275)) (fmax (- 0.175 (sqrt (+ (pow (+ 6.3 (* x 8.13008)) 2) (pow (- (* y 8.13008) 6.525) 2)))) (- (sqrt (+ (pow (+ 6.3 (* x 8.13008)) 2) (pow (- (* y 8.13008) 6.525) 2))) 0.275))))))))))))))) ================================================ FILE: bench/graphics/fidget/quarter.fpcore ================================================ (FPCore (x y) :name "A quarter-circle in the lower-left quadrant" :precision binary64 (fmax (- (+ (pow y 2) (pow x 2)) 0.5) (fmax x y)) ) ================================================ FILE: bench/graphics/lod.fpcore ================================================ ; -*- mode: scheme -*- ; ; Level-of-Detail computation ; Direct3D 11.3 ; Section 7.8.11 ; ; Texture limits from Section 21: ; Textures sizes are integer values between [1, 2^14]. ; Maximum ratio of anisotropy will be fixed at 16 since the algorithm ; is only interesting when clamping is kept minimal. ; ; While the section mentions "reasonable" limits for derivatives, ; unreasonable is still allowable, so any good algorithm should ; be able to handle inputs in this space. The limits on derivatives ; are conservative and handle inputs far outside the "reasonable" space ; ; ; ; Anisotropic ; (FPCore (w h dX.u dX.v dY.u dY.v maxAniso) :name "Anisotropic x16 LOD (LOD)" :precision binary32 :pre (and (<= 1 w 16384) (<= 1 h 16384) (<= 1e-20 (fabs dX.u) 1e+20) (<= 1e-20 (fabs dX.v) 1e+20) (<= 1e-20 (fabs dY.u) 1e+20) (<= 1e-20 (fabs dY.v) 1e+20) (== maxAniso 16)) (let* ([w (floor w)] [h (floor h)] [maxAniso (floor maxAniso)] [dX.u (* w dX.u)] [dX.v (* h dX.v)] [dY.u (* w dY.u)] [dY.v (* h dY.v)] [dX2 (+ (* dX.u dX.u) (* dX.v dX.v))] [dY2 (+ (* dY.u dY.u) (* dY.v dY.v))] [det (fabs (- (* dX.u dY.v) (* dX.v dY.u)))] [major2 (fmax dX2 dY2)] [major (sqrt major2)] [normMajor (/ 1 major)] [ratioAniso0 (/ major2 det)] ; first round of clamping [minor (if (> ratioAniso0 maxAniso) (/ major maxAniso) (/ det major))] [ratioAniso1 (if (> ratioAniso0 maxAniso) maxAniso ratioAniso0)] ; second round of clamping [ratioAniso (if (< minor 1.0) (fmax 1.0 (* ratioAniso1 minor)) ratioAniso1)]) (log2 minor))) (FPCore (w h dX.u dX.v dY.u dY.v maxAniso) :name "Anisotropic x16 LOD (ratio of anisotropy)" :precision binary32 :pre (and (<= 1 w 16384) (<= 1 h 16384) (<= 1e-20 (fabs dX.u) 1e+20) (<= 1e-20 (fabs dX.v) 1e+20) (<= 1e-20 (fabs dY.u) 1e+20) (<= 1e-20 (fabs dY.v) 1e+20) (== maxAniso 16)) (let* ([w (floor w)] [h (floor h)] [maxAniso (floor maxAniso)] [dX.u (* w dX.u)] [dX.v (* h dX.v)] [dY.u (* w dY.u)] [dY.v (* h dY.v)] [dX2 (+ (* dX.u dX.u) (* dX.v dX.v))] [dY2 (+ (* dY.u dY.u) (* dY.v dY.v))] [det (fabs (- (* dX.u dY.v) (* dX.v dY.u)))] [major2 (fmax dX2 dY2)] [major (sqrt major2)] [normMajor (/ 1 major)] [ratioAniso0 (/ major2 det)] ; first round of clamping [minor (if (> ratioAniso0 maxAniso) (/ major maxAniso) (/ det major))] [ratioAniso1 (if (> ratioAniso0 maxAniso) maxAniso ratioAniso0)] ; second round of clamping [ratioAniso (if (< minor 1.0) (fmax 1.0 (* ratioAniso1 minor)) ratioAniso1)]) ratioAniso)) (FPCore (w h dX.u dX.v dY.u dY.v maxAniso) :name "Anisotropic x16 LOD (line direction, u)" :precision binary32 :pre (and (<= 1 w 16384) (<= 1 h 16384) (<= 1e-20 (fabs dX.u) 1e+20) (<= 1e-20 (fabs dX.v) 1e+20) (<= 1e-20 (fabs dY.u) 1e+20) (<= 1e-20 (fabs dY.v) 1e+20) (== maxAniso 16)) (let* ([w (floor w)] [h (floor h)] [maxAniso (floor maxAniso)] [dX.u (* w dX.u)] [dX.v (* h dX.v)] [dY.u (* w dY.u)] [dY.v (* h dY.v)] [dX2 (+ (* dX.u dX.u) (* dX.v dX.v))] [dY2 (+ (* dY.u dY.u) (* dY.v dY.v))] [det (fabs (- (* dX.u dY.v) (* dX.v dY.u)))] [major2 (fmax dX2 dY2)] [major (sqrt major2)] [normMajor (/ 1 major)] [ratioAniso0 (/ major2 det)] ; first round of clamping [minor (if (> ratioAniso0 maxAniso) (/ major maxAniso) (/ det major))] [ratioAniso1 (if (> ratioAniso0 maxAniso) maxAniso ratioAniso0)] ; second round of clamping [ratioAniso (if (< minor 1.0) (fmax 1.0 (* ratioAniso1 minor)) ratioAniso1)]) (if (>= dX2 dY2) (* normMajor dX.u) (* normMajor dY.u)))) (FPCore (w h dX.u dX.v dY.u dY.v maxAniso) :name "Anisotropic x16 LOD (line direction, v)" :precision binary32 :pre (and (<= 1 w 16384) (<= 1 h 16384) (<= 1e-20 (fabs dX.u) 1e+20) (<= 1e-20 (fabs dX.v) 1e+20) (<= 1e-20 (fabs dY.u) 1e+20) (<= 1e-20 (fabs dY.v) 1e+20) (== maxAniso 16)) (let* ([w (floor w)] [h (floor h)] [maxAniso (floor maxAniso)] [dX.u (* w dX.u)] [dX.v (* h dX.v)] [dY.u (* w dY.u)] [dY.v (* h dY.v)] [dX2 (+ (* dX.u dX.u) (* dX.v dX.v))] [dY2 (+ (* dY.u dY.u) (* dY.v dY.v))] [det (fabs (- (* dX.u dY.v) (* dX.v dY.u)))] [major2 (fmax dX2 dY2)] [major (sqrt major2)] [normMajor (/ 1 major)] [ratioAniso0 (/ major2 det)] ; first round of clamping [minor (if (> ratioAniso0 maxAniso) (/ major maxAniso) (/ det major))] [ratioAniso1 (if (> ratioAniso0 maxAniso) maxAniso ratioAniso0)] ; second round of clamping [ratioAniso (if (< minor 1.0) (fmax 1.0 (* ratioAniso1 minor)) ratioAniso1)]) (if (>= dX2 dY2) (* normMajor dX.v) (* normMajor dY.v)))) ; ; Isotropic (supports 3D as well) ; (FPCore (w h d dX.u dX.v dX.w dY.u dY.v dY.w) :name "Isotropic LOD (LOD)" :precision binary32 :pre (and (<= 1 w 16384) (<= 1 h 16384) (<= 1 d 4096) (<= 1e-20 (fabs dX.u) 1e+20) (<= 1e-20 (fabs dX.v) 1e+20) (<= 1e-20 (fabs dX.w) 1e+20) (<= 1e-20 (fabs dY.u) 1e+20) (<= 1e-20 (fabs dY.v) 1e+20) (<= 1e-20 (fabs dY.w) 1e+20)) (let* ([w (floor w)] [h (floor h)] [d (floor d)] [dX.u (* w dX.u)] [dX.v (* h dX.v)] [dX.w (* d dX.w)] [dY.u (* w dY.u)] [dY.v (* h dY.v)] [dY.w (* d dY.w)] [dX2 (+ (+ (* dX.u dX.u) (* dX.v dX.v)) (* dX.w dX.w))] [dY2 (+ (+ (* dY.u dY.u) (* dY.v dY.v)) (* dY.w dY.w))] [major2 (fmax dX2 dY2)] [major (sqrt major2)]) (log2 major))) ; The following two benchmarks are thanks to Ben Wang and Bill Zorn. (FPCore (p r q) :name "1/2(abs(p)+abs(r) - sqrt((p-r)^2 + 4q^2))" (* (/ 1.0 2.0) (- (+ (fabs p) (fabs r)) (sqrt (+ (pow (- p r) 2.0) (* 4.0 (pow q 2.0))))))) (FPCore (p r q) :name "1/2(abs(p)+abs(r) + sqrt((p-r)^2 + 4q^2))" (* (/ 1.0 2.0) (+ (+ (fabs p) (fabs r)) (sqrt (+ (pow (- p r) 2.0) (* 4.0 (pow q 2.0))))))) ================================================ FILE: bench/graphics/log-transform.fpcore ================================================ ; This example is taken from the Image Processing domain and referred to ; as "Logarithmic Transform". This in its basic form is used to bring out ; details in dark parts of an image. This instantiation of the idea is for 1D ; as opposed to 2D images. (FPCore (c x y) :name "Logarithmic Transform" :alt (* c (log1p (* (expm1 x) y))) (* c (log (+ 1.0 (* (- (pow E x) 1.0) y))))) ================================================ FILE: bench/graphics/pbrt.fpcore ================================================ (FPCore (cosTheta_i cosTheta_O sinTheta_i sinTheta_O v) :name "HairBSDF, Mp, upper" :precision binary32 :pre (and (<= -1 cosTheta_i 1) (<= -1 cosTheta_O 1) (<= -1 sinTheta_i 1) (<= -1 sinTheta_O 1) (< 0.1 v) (<= v 1.5707964)) (let ([a (/ (* cosTheta_i cosTheta_O) v)] [b (/ (* sinTheta_i sinTheta_O) v)]) (/ (* (exp (- b)) a) (* (* (sinh (/ 1 v)) 2) v)))) (FPCore (cosTheta_i cosTheta_O sinTheta_i sinTheta_O v) :name "HairBSDF, Mp, lower" :precision binary32 :pre (and (<= -1 cosTheta_i 1) (<= -1 cosTheta_O 1) (<= -1 sinTheta_i 1) (<= -1 sinTheta_O 1) (<= -1.5707964 v 0.1)) (let ([a (/ (* cosTheta_i cosTheta_O) v)] [b (/ (* sinTheta_i sinTheta_O) v)]) (exp (+ (+ (- (- a b) (/ 1 v)) 0.6931f0) (log (/ 1 (* 2 v))))))) (FPCore (u v) :name "HairBSDF, sample_f, cosTheta" :precision binary32 :pre (and (<= 1e-5 u 1) (<= 0 v 109.746574)) (+ 1 (* v (log (+ u (* (- 1 u) (exp (/ -2 v)))))))) (FPCore (sinTheta_O h eta) :name "HairBSDF, gamma for a refracted ray" :precision binary32 :pre (and (<= -1 sinTheta_O 1) (<= -1 h 1) (<= 0 eta 10)) (let* ([sqr_sinTheta_O (* sinTheta_O sinTheta_O)] [cosThetaO (sqrt (- 1 sqr_sinTheta_O))]) (asin (/ h (sqrt (- (* eta eta) (/ sqr_sinTheta_O cosThetaO))))))) ;; Light sampling functions (FPCore (ux uy maxCos) :name "UniformSampleCone, x" :precision binary32 :pre (and (<= 2.328306437e-10 ux 1) (<= 2.328306437e-10 uy 1) (<= 0 maxCos 1)) (let* ([cosTheta (+ (- 1 ux) (* ux maxCos))] [sinTheta (sqrt (- 1 (* cosTheta cosTheta)))] [phi (* (* uy 2) PI)]) (* (cos phi) sinTheta))) (FPCore (ux uy maxCos) :name "UniformSampleCone, y" :precision binary32 :pre (and (<= 2.328306437e-10 ux 1) (<= 2.328306437e-10 uy 1) (<= 0 maxCos 1)) (let* ([cosTheta (+ (- 1 ux) (* ux maxCos))] [sinTheta (sqrt (- 1 (* cosTheta cosTheta)))] [phi (* (* uy 2) PI)]) (* (sin phi) sinTheta))) (FPCore (ux uy maxCos) :name "UniformSampleCone, z" :precision binary32 :pre (and (<= 2.328306437e-10 ux 1) (<= 2.328306437e-10 uy 1) (<= 0 maxCos 1)) (let* ([cosTheta (+ (- 1 ux) (* ux maxCos))] [sinTheta (sqrt (- 1 (* cosTheta cosTheta)))] [phi (* (* uy 2) PI)]) cosTheta)) ;; Unsure on reasonable bounds for xi, yi, zi (FPCore (xi yi zi ux uy maxCos) :name "UniformSampleCone 2" :precision binary32 :pre (and (<= -1e4 xi 1e4) (<= -1e4 yi 1e4) (<= -1e4 zi 1e4) (<= 2.328306437e-10 ux 1) (<= 2.328306437e-10 uy 1) (<= 0 maxCos 1)) (let* ([cosTheta (* (* (- 1 ux) maxCos) ux)] ; (lerp ux maxCos 1) [sinTheta (sqrt (- 1 (* cosTheta cosTheta)))] [phi (* (* uy 2) PI)]) (+ (+ (* (* (cos phi) sinTheta) xi) (* (* (sin phi) sinTheta) yi)) (* cosTheta zi)))) (FPCore (normAngle u n0_i n1_i) :name "Curve intersection, scale width based on ribbon orientation" :precision binary32 :pre (and (<= 0 normAngle (/ PI 2)) (<= -1 n0_i 1) (<= -1 n1_i 1) (<= 2.328306437e-10 u 1)) (let* ([invSinNormAngle (/ 1 (sin normAngle))] [sin0 (* (sin (* (- 1 u) normAngle)) invSinNormAngle)] [sin1 (* (sin (* u normAngle)) invSinNormAngle)]) (+ (* sin0 n0_i) (* sin1 n1_i)))) (FPCore (x tau) :name "Lanczos kernel" :precision binary32 :pre (and (<= 1e-5 x 1) (<= 1 tau 5)) (let ([xp (* x PI)]) (* (/ (sin (* xp tau)) (* xp tau)) (/ (sin xp) xp)))) (FPCore (cosTheta_i u1 u2) :name "Beckmann Sample, near normal, slope_x" :precision binary32 :pre (and (> cosTheta_i 0.9999) (<= cosTheta_i 1) (<= 2.328306437e-10 u1 1) (<= 2.328306437e-10 u2 1)) (let ([r (sqrt (- (log (- 1 u1))))] [sinPhi (sin (* (* 2 PI) u2))] [cosPhi (cos (* (* 2 PI) u2))]) (* r cosPhi))) (FPCore (cosTheta_i u1 u2) :name "Beckmann Sample, near normal, slope_y" :precision binary32 :pre (and (> cosTheta_i 0.9999) (<= cosTheta_i 1) (<= 2.328306437e-10 u1 1) (<= 2.328306437e-10 u2 1)) (let ([r (sqrt (- (log (- 1 u1))))] [sinPhi (sin (* (* 2 PI) u2))] [cosPhi (cos (* (* 2 PI) u2))]) (* r sinPhi))) (FPCore (cosTheta_i u1 u2) :name "Trowbridge-Reitz Sample, near normal, slope_x" :precision binary32 :pre (and (> cosTheta_i 0.9999) (<= cosTheta_i 1) (<= 2.328306437e-10 u1 1) (<= 2.328306437e-10 u2 1)) (let ([r (sqrt (/ u1 (- 1 u1)))] [phi (* 6.28318530718 u2)]) (* r (cos phi)))) (FPCore (cosTheta_i u1 u2) :name "Trowbridge-Reitz Sample, near normal, slope_y" :precision binary32 :pre (and (> cosTheta_i 0.9999) (<= cosTheta_i 1) (<= 2.328306437e-10 u1 1) (<= 2.328306437e-10 u2 1)) (let ([r (sqrt (/ u1 (- 1 u1)))] [phi (* 6.28318530718 u2)]) (* r (sin phi)))) (FPCore (u0 u1 alphax alphay) :name "Trowbridge-Reitz Sample, sample surface normal, cosTheta" :precision binary32 :pre (and (<= 2.328306437e-10 u0 1) (<= 2.328306437e-10 u1 0.5) (<= 0.0001 alphax 1) (<= 0.0001 alphay 1)) (let* ([phi (atan (* (/ alphay alphax) (tan (+ (* (* 2 PI) u1) (* 0.5 PI)))))] [sinPhi (sin phi)] [cosPhi (cos phi)] [alphax2 (* alphax alphax)] [alphay2 (* alphay alphay)] [alpha2 (/ 1 (+ (/ (* cosPhi cosPhi) alphax2) (/ (* sinPhi sinPhi) alphay2)))] [tanTheta2 (/ (* alpha2 u0) (- 1 u0))]) (/ 1 (sqrt (+ 1 tanTheta2))))) (FPCore (x s) :name "Logistic distribution" :precision binary32 :pre (<= 0 s 1.0651631) (let ([e (exp (/ (- (fabs x)) s))]) (/ e (* (* s (+ 1 e)) (+ 1 e))))) (FPCore (x s) :name "Logistic function" :precision binary32 :pre (<= 0 s 1.0651631) (/ 1 (+ 1 (exp (/ (- x) s))))) (FPCore (u s) :name "Sample trimmed logistic on [-pi, pi]" :precision binary32 :pre (and (<= 2.328306437e-10 u 1) (<= 0 s 1.0651631)) (let* ([lcdf_lo (/ 1 (+ 1 (exp (/ PI s))))] [lcdf_hi (/ 1 (+ 1 (exp (/ (- PI) s))))] [k (- lcdf_hi lcdf_lo)]) (* (- s) (log (- (/ 1 (+ (* u k) lcdf_lo)) 1))))) (FPCore (cosTheta c) :name "Beckmann Sample, normalization factor" :precision binary32 :pre (and (< 0 cosTheta 0.9999) (< -1 c 1)) (let* ([sinTheta (sqrt (- (- 1 cosTheta) cosTheta))] [tanTheta (/ sinTheta cosTheta)]) (/ 1 (+ (+ 1 c) (* (* (/ 1 (sqrt PI)) tanTheta) (exp (* (- cosTheta) cosTheta))))))) (FPCore (alpha u0) :name "Beckmann Distribution sample, tan2theta, alphax == alphay" :precision binary32 :pre (and (<= 0.0001 alpha 1) (<= 2.328306437e-10 u0 1)) (* (* (- alpha) alpha) (log (- 1 u0)))) (FPCore (alphax alphay u0 cos2phi sin2phi) :name "Beckmann Distribution sample, tan2theta, alphax != alphay, u1 <= 0.5" :precision binary32 :pre (and (<= 0.0001 alphax 1) (<= 0.0001 alphay 1) (<= 2.328306437e-10 u0 1) (<= 0 cos2phi 1) (<= 0 sin2phi)) (let ([alphax2 (* alphax alphax)] [alphay2 (* alphay alphay)]) (/ (- (log (- 1 u0))) (+ (/ cos2phi alphax2) (/ sin2phi alphay2))))) ; TODO: find better upper bound on 's' for Disney BSSRDF... (FPCore (s r) :name "Disney BSSRDF, PDF of scattering profile" :precision binary32 :pre (and (<= 0 s 256) (< 1e-6 r 1e6)) (+ (/ (* 0.25 (exp (/ (- r) s))) (* (* (* 2 PI) s) r)) (/ (* 0.75 (exp (/ (- r) (* 3 s)))) (* (* (* 6 PI) s) r)))) (FPCore (s u) :name "Disney BSSRDF, sample scattering profile, lower" :precision binary32 :pre (and (<= 0 s 256) (<= 2.328306437e-10 u 0.25)) (* s (log (/ 1 (- 1 (* 4 u)))))) (FPCore (s u) :name "Disney BSSRDF, sample scattering profile, upper" :precision binary32 :pre (and (<= 0 s 256) (<= 0.25 u 1)) (* (* 3 s) (log (/ 1 (- 1 (/ (- u 0.25) 0.75)))))) (FPCore (cosTheta alpha) :name "GTR1 distribution" :precision binary32 :pre (and (<= 0 cosTheta 1) (<= 0.0001 alpha 1)) (let ([alpha2 (* alpha alpha)]) (/ (- alpha2 1) (* (* PI (log alpha2)) (+ 1 (* (* (- alpha2 1) cosTheta) cosTheta)))))) ================================================ FILE: bench/hamming/machine-decide.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a x) :name "expax (section 3.5)" ;:herbie-expected 14 :pre (> 710 (* a x)) ; Hamming suggests using Taylor expansion for ax small ; We use expm1 as it specifically avoids cancellation for ax small :alt (! :herbie-platform c (expm1 (* a x))) (- (exp (* a x)) 1)) ================================================ FILE: bench/hamming/overflow-underflow.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "expq2 (section 3.11)" :pre (> 710 x) ; Hamming version (1/1 - e^-x) was not accurate. Dealt with overflow/underflow but not cancellation. ; Used expm1 from libm to deal with cancellation. :alt (! :herbie-platform c (/ (- 1) (expm1 (- x)))) (/ (exp x) (- (exp x) 1))) ================================================ FILE: bench/hamming/quadratic.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a b c) :name "quadp (p42, positive)" :herbie-expected 10 ; From Racket Math Lib implementation :alt (! :herbie-platform c (let ([sqtD (let ([x (* (sqrt (fabs a)) (sqrt (fabs c)))]) (if (== (copysign a c) a) (* (sqrt (- (fabs (/ b 2)) x)) (sqrt (+ (fabs (/ b 2)) x))) (hypot (/ b 2) x)))]) (if (< b 0) (/ (- sqtD (/ b 2)) a) (/ (- c) (+ (/ b 2) sqtD))))) (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (+ (- b) d) (* 2 a)))) (FPCore (a b c) :name "quadm (p42, negative)" :herbie-expected 10 ; From Racket Math Lib implementation :alt (! :herbie-platform c (let ([sqtD (let ([x (* (sqrt (fabs a)) (sqrt (fabs c)))]) (if (== (copysign a c) a) (* (sqrt (- (fabs (/ b 2)) x)) (sqrt (+ (fabs (/ b 2)) x))) (hypot (/ b 2) x)))]) (if (< b 0) (/ c (- sqtD (/ b 2))) (/ (+ (/ b 2) sqtD) (- a))))) (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (- (- b) d) (* 2 a)))) (FPCore (a b_2 c) :name "quad2m (problem 3.2.1, negative)" :herbie-expected 10 ; From Racket Math Lib implementation :alt (! :herbie-platform c (let ([sqtD (let ([x (* (sqrt (fabs a)) (sqrt (fabs c)))]) (if (== (copysign a c) a) (* (sqrt (- (fabs b_2) x)) (sqrt (+ (fabs b_2) x))) (hypot b_2 x)))]) (if (< b_2 0) (/ c (- sqtD b_2)) (/ (+ b_2 sqtD) (- a))))) (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) (/ (- (- b_2) d) a))) (FPCore (a b_2 c) :name "quad2p (problem 3.2.1, positive)" :herbie-expected 10 ; From Racket Math Lib implementation :alt (! :herbie-platform c (let ([sqtD (let ([x (* (sqrt (fabs a)) (sqrt (fabs c)))]) (if (== (copysign a c) a) (* (sqrt (- (fabs b_2) x)) (sqrt (+ (fabs b_2) x))) (hypot b_2 x)))]) (if (< b_2 0) (/ (- sqtD b_2) a) (/ (- c) (+ b_2 sqtD))))) (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) (/ (+ (- b_2) d) a))) ================================================ FILE: bench/hamming/rearrangement.fpcore ================================================ ; -*- mode: scheme -*- ; The precondition for this expression is labelled as for large x hence we choose this ; range that contains all the relevant values that are sampled. The proof that they ; are equivalent is by rationalizing the denominator by multiplying both the ; numerator and denominator by the conjugate of the denominator and simplifying using ; the identity a^2-b^2 = (a+b)(a-b) and cancellation. (FPCore (x) :name "2sqrt (example 3.1)" :pre (and (> x 1) (< x 1e+308)) :alt (! :herbie-platform c (/ 1 (+ (sqrt (+ x 1)) (sqrt x)))) :alt (! :herbie-platform c (* 0.5 (pow x -0.5))) (- (sqrt (+ x 1)) (sqrt x))) ; The precondition for this expression is labelled as for large x hence we choose this ; range that contains all the relevant values that are sampled. The proof that they ; are equivalent is by rationalizing the denominator by multiplying both the ; numerator and denominator by the conjugate of the denominator and simplifying using ; the identity a^2-b^2 = (a+b)(a-b) and cancellation. (FPCore (x) :name "2isqrt (example 3.6)" :pre (and (> x 1) (< x 1e+308)) :alt (! :herbie-platform c (/ 1 (+ (* (+ x 1) (sqrt x)) (* x (sqrt (+ x 1)))))) :alt (! :herbie-platform c (- (pow x -0.5) (pow (+ x 1) -0.5))) (- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))) (FPCore (x) :name "2frac (problem 3.3.1)" :alt (! :herbie-platform c (/ (/ -1 x) (+ x 1))) :alt (! :herbie-platform c (/ 1 (* x (- -1 x)))) (- (/ 1 (+ x 1)) (/ 1 x))) (FPCore (x) :name "3frac (problem 3.3.3)" ; Rewrite comes from Hammings textbook on page 46. Hamming says to sample large x ; Half of floating point numbers are > 1 so this will sample large enough x. :pre (> (fabs x) 1) :alt (! :herbie-platform c (/ 2 (* x (- (* x x) 1)))) (+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))) ; The precondition for this expression is labelled as for large x hence we choose this ; range that contains all the relevant values that are sampled. The proof that they ; are equivalent is by rewriting the two terms as a = (cbrt (+ x 1)) and b = (cbrt x) ; and then using the trigonometric identity for a^3-b^3 where (a-b) is the term we ; equate to (/ (- (pow a 3) (pow b 3)) (+ (pow a 2) (* a b) (pow b 2))), and then cancel values. (FPCore (x) :name "2cbrt (problem 3.3.4)" :pre (and (> x 1) (< x 1e+308)) :herbie-expected 2.5 :alt (! :herbie-platform c (/ 1 (+ (* (cbrt (+ x 1)) (cbrt (+ x 1))) (* (cbrt x) (cbrt (+ x 1))) (* (cbrt x) (cbrt x))))) (- (cbrt (+ x 1)) (cbrt x))) ; This is a slightly unique rewrite because it involves the use of the c function log1p ; as Hamming gave this as an exercise with no real hint on how to approach this. ; For the precondition we want large values of N as described by Hamming. Hence, we chose the range for reasonable ; values for which this could be queried and have relevant sampling and still being described as a large enough N. ; log1p(x) simply returns log(1+x) but computed in a way that is accurate even when the value of number is close to zero. ; Hence, after rearranging the expression using log-rewrite to get us (log (/ (+ N 1) N)) = (log (+ 1 (/ 1 N))), we can ; find the term to be used in place of x to call log1p (FPCore (N) :name "2log (problem 3.3.6)" :pre (and (> N 1) (< N 1e+40)) :alt (! :herbie-platform c (log1p (/ 1 N))) :alt (! :herbie-platform c (log (+ 1 (/ 1 N)))) :alt (! :herbie-platform c (+ (/ 1 N) (/ -1 (* 2 (pow N 2))) (/ 1 (* 3 (pow N 3))) (/ -1 (* 4 (pow N 4))))) (- (log (+ N 1)) (log N))) ; Commented out explanation for platforms ; Talking about the else case first, this is simply a log-rewrite which is (log(/ a b)) = (- (log a) (log b)) ; with N being cancelled in the numerator and denominator. ; Now for the reason for the if-case and the 1e+3 value is because while on sampling in Herbie reports and Odyssey, ; the sampled values for this equation is extremely inaccurate after this range. There could be a possible overflow or ; another reason but we have consistently noted that this exists. Hence, we chose the next best case ; which is a Taylor series until the 4-th power, preventing too much of computation power while giving ; the best answer for that range. (FPCore (x) :name "exp2 (problem 3.3.7)" :pre (<= (fabs x) 710) ; Bounded x on the extremes by +/- 710 to avoid sampling infinite outputs ; Target comes from Hamming's textbook, page 46 :alt (! :herbie-platform c (* 4 (* (sinh (/ x 2)) (sinh (/ x 2))))) (+ (- (exp x) 2) (exp (- x)))) ================================================ FILE: bench/hamming/series.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "expm1 (example 3.7)" ; We want to sample around 0 according to Hamming. We can sample more inputs closer to zero by shrinking the range to +/- 1 :pre (<= (fabs x) 1) ; Hamming suggests taylor series expansion but we have libm functions in FPCore for the c platform :alt (! :herbie-platform c (expm1 x)) (- (exp x) 1)) (FPCore (n) :name "logs (example 3.8)" :pre (> n 6.8e+15) :alt (! :herbie-platform c (- (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)) :alt (! :herbie-platform c (if (< (fabs x) 0.026) (* (/ x 3) (+ 1 (/ (* x x) 15))) (- (/ 1 x) (/ 1 (tan x))))) (- (/ 1 x) (/ 1 (tan x)))) (FPCore (x) :name "qlog (example 3.10)" ; We want to sample small x according to Hamming. We can sample more inputs closer to zero by shrinking the range to +/- 1 :pre (<= (fabs x) 1) ; Hamming suggests using Taylor expansion but until we have platforms, we will use FPCore's access to c functions. :alt (! :herbie-platform c (/ (log1p (- x)) (log1p x))) (/ (log (- 1 x)) (log (+ 1 x)))) (FPCore (x) :name "cos2 (problem 3.4.1)" (/ (- 1 (cos x)) (* x x))) (FPCore (a b eps) :name "expq3 (problem 3.4.2)" :pre (and (<= (fabs a) 710) (<= (fabs b) 710) (<= (* 1e-27 (fmin (fabs a) (fabs b))) eps (fmin (fabs a) (fabs b)))) ; We want epsilon small compared to a and b according to Hamming. In the precondition we force eps to be smaller in magnitude ; than the smaller between a and b but don't force one to be smaller than the other in order to test to see if Herbie can figure it out. ; Bound by 710 to avoid sampling infinite values since we have an exponential. ; Chose a multiplicative factor of 1e-27 compared to a in order to not sample epsilon to close to 0 which eliminates the exponent. ; Hammings' rewrite, page 48 :alt (! :herbie-platform c (+ (/ 1 a) (/ 1 b))) (/ (* eps (- (exp (* (+ a b) eps)) 1)) (* (- (exp (* a eps)) 1) (- (exp (* b eps)) 1)))) (FPCore (eps) :name "logq (problem 3.4.3)" ; Using previous notions of small epsilon, simply < +/- 1 :pre (< (fabs eps) 1) ; Hamming suggests using Taylor expansion but FPCore has libm which is contained on the "c" platform. :alt (! :herbie-platform c (- (log1p (- eps)) (log1p eps))) (log (/ (- 1 eps) (+ 1 eps)))) (FPCore (x) :name "sqrtexp (problem 3.4.4)" (sqrt (/ (- (exp (* 2 x)) 1) (- (exp x) 1)))) (FPCore (x n) :name "2nthrt (problem 3.4.6)" (- (pow (+ x 1) (/ 1 n)) (pow x (/ 1 n)))) ================================================ FILE: bench/hamming/trigonometry.fpcore ================================================ ; -*- mode: scheme -*- ; Most of the below functions (except sintan) are subsets of the rearrangement functions. However, these ; are specific to trigonometric functions as it deals with a subtraction of two trig functions that at ; a very large value cause cancellation and need to be rewritten to ensure floating point accuracy based on ; their nature of expression (more in specific detail below). ; Almost all expressions follow this precondition : -1e4 <= x <= 1e4 and 1e-16 * (abs x) < eps < (abs x) ; Our reasoning for this stems from Hamming's explanation that eps is small with respect to x, hence for ; getting the highest accuracy we are bounding it's lowest value to be 1e-16 times (abs x). We don't ; query anything below because we don't want (x+eps) ~ x. And we also want values of x that would be usable ; and relevant for most trigonometric queries as well as those that are the most sampled in Herbie ; 2sin involves the difference of two sine functions. According to the sum-to-product trigonometric identity, ; the target for this benchmark can apply the identity (- (sin x) (sin y)) = (* 2 (cos (/ (+ x y) 2)) (sin (/ (- x y) 2))) (FPCore (x eps) :name "2sin (example 3.3)" :pre (and (<= -1e4 x) (<= x 1e4) (< (* 1e-16 (fabs x)) eps) (< eps (* (fabs x)))) :alt (! :herbie-platform c (* 2 (cos (+ x (/ eps 2))) (sin (/ eps 2)))) :alt (! :herbie-platform c (+ (* (sin x) (- (cos eps) 1)) (* (cos x) (sin eps)))) :alt (! :herbie-platform c (* (cos (* 0.5 (- eps (* -2 x)))) (sin (* 0.5 eps)) 2)) (- (sin (+ x eps)) (sin x))) ; 2cos involves the difference of two cos functions. According to the sum-to-product trigonometric identity, ; the target for this benchmark can apply the identity (- (cos x) (cos y)) = (* 2 (sin (/ (+ x y) 2)) (sin (/ (- x y) 2))) (FPCore (x eps) :name "2cos (problem 3.3.5)" :pre (and (<= -1e4 x) (<= x 1e4) (< (* 1e-16 (fabs x)) eps) (< eps (* (fabs x)))) :alt (! :herbie-platform c (* -2 (sin (+ x (/ eps 2))) (sin (/ eps 2)))) :alt (! :herbie-platform c (pow (cbrt (* -2 (sin (* 0.5 (fma 2.0 x eps))) (sin (* 0.5 eps)) )) 3)) (- (cos (+ x eps)) (cos x))) ; 2tan involves the difference of two tan functions. A slightly more intuitive proof, this involves ; converting the two tan into (- (/ (sin (+ x eps) (cos (+ x eps)))) (/ (sin x) (cos x))). After cross ; multiplying we get the massive formula (/ (- (* (sin (+ x eps)) (cos x)) (* (sin x) (cos (+ x eps)))) (* (cos (+ x eps)) (cos x))) ; Fortunately, the numerator is just an expansion of the identity sin(a-b) where a = x+eps and b = x. ; Hence, we can simply use that identity to craft the numerator and leaving the denominator as is. (FPCore (x eps) :name "2tan (problem 3.3.2)" :pre (and (<= -1e4 x) (<= x 1e4) (< (* 1e-16 (fabs x)) eps) (< eps (* (fabs x)))) :alt (! :herbie-platform c (/ (sin eps) (* (cos x) (cos (+ x eps))))) :alt (! :herbie-platform c (- (/ (+ (tan x) (tan eps)) (- 1 (* (tan x) (tan eps)))) (tan x))) :alt (! :herbie-platform c (+ eps (* eps (tan x) (tan x)))) (- (tan (+ x eps)) (tan x))) ; This is directly taken from the Hamming textbook but this is a direct identity of the trig half identity ; for tan, and we completely remove any trace of a subtraction using this, fulfilling our goal. ; As a result, we can query any value of x from -infinity to +infinity as we eliminated any cause ; of the error using this rewrite. (FPCore (x) :name "tanhf (example 3.4)" :alt (! :herbie-platform c (tan (/ x 2))) (/ (- 1 (cos x)) (sin x))) ; Noting the difference in the variable name, N is actually a large N. Hence, we are changing our precondition ; definition by including all values of N till 1e+100. ; For the rewrite, we use the trigonometric identity, (- (atan x) (atan y)) = (atan (/ (- x y) (+ 1 (* x y)))) (FPCore (N) :name "2atan (example 3.5)" :pre (and (> N 1) (< N 1e+100)) :alt (! :herbie-platform c (atan (/ 1 (+ 1 (* N (+ N 1)))))) :alt (! :herbie-platform c (atan2 1 (fma N (+ 1 N) 1))) (- (atan (+ N 1)) (atan N))) ; This is a slightly unique rewrite because it involves two different trigonometric functions that don't have ; a direct rewrite. Moreover, Hamming gave this as an exercise with no real hint on how to approach this. ; Hence, we resort to the Taylor series of this identity where the rewrite for all values for (<= (fabs x) 1) ; rewrites to a Taylor series otherwise the general identity works great enough. ; The reason for this is because when x is in the vicinity of 0, both the numerator and denominator have A ; a lot of error according to the sampling, either possibly because of underflow and/or because x ~ (sin x) ~ (tan x). ; Hence, the Taylor series until the 6-th power is chosen to not prevent too much of computation power while giving ; the best answer for that range. (FPCore (eps) :name "sintan (problem 3.4.5)" :pre (and (<= -0.4 eps) (<= eps 0.4)) :alt (! :herbie-platform c (+ -0.5 (/ (* 9 (* eps eps)) 40) (/ (* -27 (* eps eps eps eps)) 2800) (/ (* 27 (* eps eps eps eps eps eps)) 112000))) :alt (! :herbie-platform c (- (* 0.225 eps eps) 0.5)) (/ (- eps (sin eps)) (- eps (tan eps)))) ================================================ FILE: bench/haskell.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x y) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, A" (sqrt (+ x y))) (FPCore (x y z t) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, B" (/ (- (+ x y) z) (* t 2.0))) (FPCore (x y) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, C" (sqrt (fabs (- x y)))) (FPCore (x y z t) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, D" :alt (! :herbie-platform default (- 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" :alt (! :herbie-platform default (if (< y -1.0761266216389975e-10) (+ x (/ 1 (/ (/ a (- z t)) y))) (if (< y 2.894426862792089e-49) (+ x (/ (* y (- z t)) a)) (+ x (/ y (/ a (- z t))))))) (+ x (/ (* y (- z t)) a))) (FPCore (x y z t a) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, F" :alt (! :herbie-platform default (if (< y -1.0761266216389975e-10) (- x (/ 1 (/ (/ a (- z t)) y))) (if (< y 2.894426862792089e-49) (- x (/ (* y (- z t)) a)) (- x (/ y (/ a (- z t))))))) (- x (/ (* y (- z t)) a))) (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, G" (* (+ x y) (+ z 1.0))) (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, H" (* (+ x y) (- 1.0 z))) (FPCore (x y z) :name "Optimisation.CirclePacking:place from circle-packing-0.1.0.4, I" (+ (+ x y) z)) (FPCore (x y z t a b c i j) :name "Data.Colour.Matrix:determinant from colour-2.3.3, A" :alt (! :herbie-platform default (if (< x -1.469694296777705e-64) (+ (- (* x (- (* y z) (* t a))) (/ (* 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 (- (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)))) (* j (- (* c a) (* y i))))) (FPCore (x y z t a) :name "Data.Colour.Matrix:inverse from colour-2.3.3, B" :alt (! :herbie-platform default (if (< z -2.468684968699548e+170) (- (* (/ y a) x) (* (/ t a) z)) (if (< z 6.309831121978371e-71) (/ (- (* x y) (* z t)) a) (- (* (/ y a) x) (* (/ t a) z))))) (/ (- (* x y) (* z t)) a)) (FPCore (x y) :name "Data.Colour.CIE.Chromaticity:chromaCoords from colour-2.3.3" (- (- 1.0 x) y)) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, A" (/ (+ x y) 2.0)) (FPCore (x y z t a) :name "Data.Colour.RGB:hslsv from colour-2.3.3, B" :alt (! :herbie-platform default (+ (/ 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" :alt (! :herbie-platform default (- (/ 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" :alt (! :herbie-platform default (- (/ x (+ x y)) (/ y (+ x y)))) (/ (- x y) (+ x y))) (FPCore (x y) :name "Data.Colour.RGB:hslsv from colour-2.3.3, E" :alt (! :herbie-platform default (- 1 (/ y x))) (/ (- x y) x)) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, A" (- (+ x y) (* x y))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, B" :alt (! :herbie-platform default (+ x (* x y))) (* x (+ y 1.0))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, C" (- (* x 2.0) y)) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, D" (+ 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" :alt (! :herbie-platform default (- 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" (+ x (/ 1.0 3.0))) (FPCore (x) :name "Data.Colour.RGBSpace.HSL:hsl from colour-2.3.3, G" (- x (/ 1.0 3.0))) (FPCore (x y) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, H" (* x (- 1.0 y))) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, I" (* x (- 1.0 (* y z)))) (FPCore (x y z) :name "Data.Colour.RGBSpace.HSV:hsv from colour-2.3.3, J" :alt (! :herbie-platform default (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) (- (* (* x y) z) (- (* x z) x)) (+ x (* (- 1.0 y) (* (- z) x)))))) (* x (- 1.0 (* (- 1.0 y) z)))) (FPCore (x y) :name "Data.Colour.SRGB:invTransferFunction from colour-2.3.3" (/ (+ x y) (+ y 1.0))) (FPCore (x y) :name "Data.Colour.SRGB:transferFunction from colour-2.3.3" (- (* (+ x 1.0) y) x)) (FPCore (x) :name "Data.Colour.CIE:lightness from colour-2.3.3" (- (* x 116.0) 16.0)) (FPCore (x) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, A" (+ (* (/ 841.0 108.0) x) (/ 4.0 29.0))) (FPCore (x y) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, B" (* 500.0 (- x y))) (FPCore (x y) :name "Data.Colour.CIE:cieLABView from colour-2.3.3, C" (* 200.0 (- x y))) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, A" :alt (! :herbie-platform default (* 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" (/ (+ x 16.0) 116.0)) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, C" (+ x (/ y 500.0))) (FPCore (x y) :name "Data.Colour.CIE:cieLAB from colour-2.3.3, D" (- x (/ y 200.0))) (FPCore (x y z t a) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, A" :alt (! :herbie-platform default (if (< z -32113435955957344.0) (- (/ x (- t (* a z))) (/ y (- (/ t z) a))) (if (< z 3.5139522372978296e-86) (* (- x (* y z)) (/ 1 (- t (* a z)))) (- (/ x (- t (* a z))) (/ y (- (/ t z) a)))))) (/ (- x (* y z)) (- t (* a z)))) (FPCore (x y z t) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, B" :alt (! :herbie-platform default (if (< x -1.618195973607049e+50) (/ 1 (- (/ y x) (* (/ z x) t))) (if (< x 2.1378306434876444e+131) (/ x (- y (* z t))) (/ 1 (- (/ y x) (* (/ z x) t)))))) (/ x (- y (* z t)))) (FPCore (x y z) :name "Diagrams.Solve.Tridiagonal:solveTriDiagonal from diagrams-solve-0.1, C" :alt (! :herbie-platform default (/ (+ 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" :alt (! :herbie-platform default (if (< z -4.262230790519429e-138) (/ (* x y) z) (if (< z 1.7042130660650472e-164) (/ x (/ z y)) (* (/ x z) y)))) (/ (* x y) z)) (FPCore (x y z t a b) :name "Diagrams.Solve.Tridiagonal:solveCyclicTriDiagonal from diagrams-solve-0.1, B" :alt (! :herbie-platform default (if (< t -1.3659085366310088e-271) (* 1 (* (+ x (* (/ y t) z)) (/ 1 (+ (+ a 1.0) (* (/ y t) b))))) (if (< t 3.036967103737246e-130) (/ z b) (* 1 (* (+ x (* (/ y t) z)) (/ 1 (+ (+ a 1.0) (* (/ y t) b)))))))) (/ (+ x (/ (* y z) t)) (+ (+ a 1.0) (/ (* y b) t)))) (FPCore (x y z) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, A" (- x (* (* y 4.0) z))) (FPCore (x y z) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, B" (* (/ 1.0 2.0) (+ x (* y (sqrt z))))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:quadForm from diagrams-solve-0.1, C" (/ x (* y 2.0))) (FPCore (x y z t a b) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, A" :alt (! :herbie-platform default (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)))) (+ (- (* x 2.0) (* (* (* y 9.0) z) t)) (* (* a 27.0) b))) (FPCore (x y z) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, B" :alt (! :herbie-platform default (- (* x (* 3.0 y)) z)) (- (* (* x 3.0) y) z)) (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, C" :alt (! :herbie-platform default (/ (/ x y) 3.0)) (/ x (* y 3.0))) (FPCore (x y z t) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, D" :alt (! :herbie-platform default (/ (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" :alt (! :herbie-platform default (if (< t -1.6210815397541398e-69) (- (- (* (* 18.0 t) (* (* x y) z)) (* (+ (* a t) (* i x)) 4.0)) (- (* (* k j) 27.0) (* c b))) (if (< t 165.68027943805222) (+ (- (* (* 18.0 y) (* x (* z t))) (* (+ (* a t) (* i x)) 4.0)) (- (* c b) (* 27.0 (* k j)))) (- (- (* (* 18.0 t) (* (* x y) z)) (* (+ (* a t) (* i x)) 4.0)) (- (* (* k j) 27.0) (* c b)))))) (- (- (+ (- (* (* (* (* x 18.0) y) z) t) (* (* a 4.0) t)) (* b c)) (* (* x 4.0) i)) (* (* j 27.0) k))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, F" (* (* x 27.0) y)) (FPCore (x y) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, G" :alt (! :herbie-platform default (/ (+ 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" :alt (! :herbie-platform default (+ (- 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" :alt (! :herbie-platform default (if (< a -2.090464557976709e+86) (- (* 0.5 (/ (* y x) a)) (* 4.5 (/ t (/ a z)))) (if (< a 2.144030707833976e+99) (/ (- (* x y) (* z (* 9.0 t))) (* a 2.0)) (- (* (/ y a) (* x 0.5)) (* (/ t a) (* z 4.5)))))) (/ (- (* x y) (* (* z 9.0) t)) (* a 2.0))) (FPCore (x y z t a b c) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, J" :alt (! :herbie-platform default (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) -1.100156740804105e-171) (/ (+ (- (* (* x 9.0) y) (* (* z 4.0) (* t a))) b) (* z c)) (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) -0.0) (/ (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) z) c) (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) 1.1708877911747488e-53) (/ (+ (- (* (* x 9.0) y) (* (* z 4.0) (* t a))) b) (* z c)) (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) 2.876823679546137e+130) (- (+ (* (* 9.0 (/ y c)) (/ x z)) (/ b (* c z))) (* 4.0 (/ (* a t) c))) (if (< (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c)) 1.3838515042456319e+158) (/ (+ (- (* (* x 9.0) y) (* (* z 4.0) (* t a))) b) (* z c)) (- (+ (* 9.0 (* (/ y (* c z)) x)) (/ b (* c z))) (* 4.0 (/ (* a t) c))))))))) (/ (+ (- (* (* x 9.0) y) (* (* (* z 4.0) t) a)) b) (* z c))) (FPCore (x y z t a b) :name "Diagrams.Solve.Polynomial:cubForm from diagrams-solve-0.1, K" :alt (! :herbie-platform default (if (< z -1.3793337487235141e+129) (- (* (* 2.0 (sqrt x)) (cos (- (/ 1 y) (/ (/ 0.3333333333333333 z) t)))) (/ (/ a 3.0) b)) (if (< z 3.516290613555987e+106) (- (* (* (sqrt x) 2.0) (cos (- y (* (/ t 3.0) z)))) (/ (/ a 3.0) b)) (- (* (cos (- y (/ (/ 0.3333333333333333 z) t))) (* 2.0 (sqrt x))) (/ (/ a b) 3.0))))) (- (* (* 2.0 (sqrt x)) (cos (- y (/ (* z t) 3.0)))) (/ a (* b 3.0)))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, A" (- x (* (/ 3.0 8.0) y))) (FPCore (x y z t) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, B" :alt (! :herbie-platform default (- (+ (/ 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" (+ (- (+ (* 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" (- (/ (* x y) 2.0) (/ z 8.0))) (FPCore (x y) :name "Diagrams.Solve.Polynomial:quartForm from diagrams-solve-0.1, E" (- x (/ y 4.0))) (FPCore (x y) :name "Text.Parsec.Token:makeTokenParser from parsec-3.1.9, A" (/ (+ x y) 10.0)) (FPCore (x y z) :name "Text.Parsec.Token:makeTokenParser from parsec-3.1.9, B" (* (+ x y) z)) (FPCore (x y) :name "Numeric.Interval.Internal:bisect from intervals-0.7.1, A" :alt (! :herbie-platform default (* 0.5 (+ x y))) (+ x (/ (- y x) 2.0))) (FPCore (x y) :name "Numeric.Interval.Internal:scale from intervals-0.7.1, B" (/ (* x y) 2.0)) (FPCore (x y z t) :name "Linear.V2:$cdot from linear-1.19.1.3, A" (+ (* x y) (* z t))) (FPCore (x y z t) :name "Linear.V3:cross from linear-1.19.1.3" (- (* x y) (* z t))) (FPCore (x y z t a b) :name "Linear.V3:$cdot from linear-1.19.1.3, 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" (+ (+ (+ (* x y) (* z t)) (* a b)) (* c i))) (FPCore (x y z) :name "Main:bigenough2 from A" (+ x (* y (+ z x)))) (FPCore (x) :name "Main:bigenough1 from B" (+ x (* x x))) (FPCore (x) :name "Main:bigenough3 from C" :alt (! :herbie-platform default (/ 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" :alt (! :herbie-platform default (+ (* (* 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" :alt (! :herbie-platform default (* (- 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" :alt (! :herbie-platform default (* (- 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" :alt (! :herbie-platform default (* (- 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" :alt (! :herbie-platform default (+ (* 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" (+ (* x x) (* y y))) (FPCore (x y z) :name "Linear.Quaternion:$ctanh from linear-1.19.1.3" :alt (! :herbie-platform default (if (< z -4.2173720203427147e-29) (/ (* x (/ 1 (/ y (sin y)))) z) (if (< z 4.446702369113811e+64) (/ x (* z (/ y (sin y)))) (/ (* x (/ 1 (/ y (sin y)))) z)))) (/ (* x (/ (sin y) y)) z)) (FPCore (x y) :name "Linear.Quaternion:$ccosh from linear-1.19.1.3" :alt (! :herbie-platform default (* (sin x) (/ (sinh y) x))) (/ (* (sin x) (sinh y)) x)) (FPCore (x y) :name "Linear.Quaternion:$csinh from linear-1.19.1.3" :alt (! :herbie-platform default (/ (* (cosh x) (sin y)) y)) (* (cosh x) (/ (sin y) y))) (FPCore (x y z) :name "Linear.Quaternion:$ctan from linear-1.19.1.3" :alt (! :herbie-platform default (if (< y -4.618902267687042e-52) (* (/ (/ y z) x) (cosh x)) (if (< y 1.038530535935153e-39) (/ (/ (* (cosh x) y) x) z) (* (/ (/ y z) x) (cosh x))))) (/ (* (cosh x) (/ y x)) z)) (FPCore (x y) :name "Linear.Quaternion:$ccos from linear-1.19.1.3" (* (sin x) (/ (sinh y) y))) (FPCore (x y) :name "Linear.Quaternion:$csin from linear-1.19.1.3" (* (cos x) (/ (sinh y) y))) (FPCore (x y) :name "Linear.Quaternion:$clog from linear-1.19.1.3" :alt (! :herbie-platform default (if (< x -1.5097698010472593e+153) (- (+ (* 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" (* 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" :alt (! :herbie-platform default (if (< y4 -7.206256231996481e+60) (- (- (* (- (* b a) (* i c)) (- (* y x) (* t z))) (- (* (- (* j x) (* k z)) (- (* y0 b) (* i y1))) (* (- (* j t) (* k y)) (- (* y4 b) (* y5 i))))) (- (/ (- (* y2 t) (* y3 y)) (/ 1 (- (* y4 c) (* y5 a)))) (* (- (* y2 k) (* y3 j)) (- (* y4 y1) (* y5 y0))))) (if (< y4 -3.364603505246317e-66) (+ (- (- (- (* (* t c) (* i z)) (* (* a t) (* b z))) (* (* y c) (* i x))) (* (- (* b y0) (* i y1)) (- (* j x) (* k z)))) (- (* (- (* y0 c) (* a y1)) (- (* x y2) (* z y3))) (- (* (- (* t y2) (* y y3)) (- (* y4 c) (* a y5))) (* (- (* y1 y4) (* y5 y0)) (- (* k y2) (* j y3)))))) (if (< y4 -1.2000065055686116e-105) (+ (+ (- (* (- (* j t) (* k y)) (- (* y4 b) (* y5 i))) (* (* y3 y) (- (* y5 a) (* y4 c)))) (+ (* (* y5 a) (* t y2)) (* (- (* k y2) (* j y3)) (- (* y4 y1) (* y5 y0))))) (- (* (- (* x y2) (* z y3)) (- (* c y0) (* a y1))) (- (* (- (* b y0) (* i y1)) (- (* j x) (* k z))) (* (- (* y x) (* z t)) (- (* b a) (* i c)))))) (if (< y4 6.718963124057495e-279) (+ (- (- (- (* (* k y) (* y5 i)) (* (* y b) (* y4 k))) (* (* y5 t) (* i j))) (- (* (- (* y2 t) (* y3 y)) (- (* y4 c) (* y5 a))) (* (- (* y2 k) (* y3 j)) (- (* y4 y1) (* y5 y0))))) (- (* (- (* b a) (* i c)) (- (* y x) (* t z))) (- (* (- (* j x) (* k z)) (- (* y0 b) (* i y1))) (* (- (* y2 x) (* y3 z)) (- (* c y0) (* y1 a)))))) (if (< y4 4.77962681403792e-222) (+ (+ (- (* (- (* j t) (* k y)) (- (* y4 b) (* y5 i))) (* (* y3 y) (- (* y5 a) (* y4 c)))) (+ (* (* y5 a) (* t y2)) (* (- (* k y2) (* j y3)) (- (* y4 y1) (* y5 y0))))) (- (* (- (* x y2) (* z y3)) (- (* c y0) (* a y1))) (- (* (- (* b y0) (* i y1)) (- (* j x) (* k z))) (* (- (* y x) (* z t)) (- (* b a) (* i c)))))) (if (< y4 2.2852241541266835e-175) (+ (- (- (- (* (* k y) (* y5 i)) (* (* y b) (* y4 k))) (* (* y5 t) (* i j))) (- (* (- (* y2 t) (* y3 y)) (- (* y4 c) (* y5 a))) (* (- (* y2 k) (* y3 j)) (- (* y4 y1) (* y5 y0))))) (- (* (- (* b a) (* i c)) (- (* y x) (* t z))) (- (* (- (* j x) (* k z)) (- (* y0 b) (* i y1))) (* (- (* y2 x) (* y3 z)) (- (* c y0) (* y1 a)))))) (+ (- (+ (+ (- (* (- (* x y) (* z t)) (- (* a b) (* c i))) (- (* k (* i (* z y1))) (+ (* j (* i (* x y1))) (* y0 (* k (* z b)))))) (- (* z (* y3 (* a y1))) (+ (* y2 (* x (* a y1))) (* y0 (* z (* c y3)))))) (* (- (* t j) (* y k)) (- (* y4 b) (* y5 i)))) (* (- (* t y2) (* y y3)) (- (* y4 c) (* y5 a)))) (* (- (* k y2) (* j y3)) (- (* y4 y1) (* y5 y0))))))))))) (+ (- (+ (+ (- (* (- (* x y) (* z t)) (- (* a b) (* c i))) (* (- (* x j) (* z k)) (- (* y0 b) (* y1 i)))) (* (- (* x y2) (* z y3)) (- (* y0 c) (* y1 a)))) (* (- (* t j) (* y k)) (- (* y4 b) (* y5 i)))) (* (- (* t y2) (* y y3)) (- (* y4 c) (* y5 a)))) (* (- (* k y2) (* j y3)) (- (* y4 y1) (* y5 y0))))) (FPCore (x y z t a b c i j) :name "Linear.Matrix:det33 from linear-1.19.1.3" :alt (! :herbie-platform default (if (< t -8.120978919195912e-33) (- (* x (- (* z y) (* a t))) (- (* b (- (* z c) (* a i))) (* (- (* c t) (* y i)) j))) (if (< t -4.712553818218485e-169) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* i a)))) (/ (* j (- (pow (* c t) 2) (pow (* i y) 2))) (+ (* c t) (* i y)))) (if (< t -7.633533346031584e-308) (- (* x (- (* z y) (* a t))) (- (* b (- (* z c) (* a i))) (* (- (* c t) (* y i)) j))) (if (< t 1.0535888557455487e-139) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* i a)))) (/ (* 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)))))))) (+ (- (* x (- (* y z) (* t a))) (* b (- (* c z) (* i a)))) (* j (- (* c t) (* i y))))) (FPCore (x y) :name "Linear.Matrix:fromQuaternion from linear-1.19.1.3, A" :alt (! :herbie-platform default (* (* 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" :alt (! :herbie-platform default (* (* 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" :alt (! :herbie-platform default (if (< t -9.231879582886777e-80) (* (* y t) (- x z)) (if (< t 2.543067051564877e+83) (* y (* t (- x z))) (* (* y (- x z)) t)))) (* (- (* x y) (* z y)) t)) (FPCore (x y z t) :name "Linear.Projection:infinitePerspective from linear-1.19.1.3, A" :alt (! :herbie-platform default (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) (/ (* (/ x z) 2.0) (- y t)) (* (/ x (* (- y t) z)) 2.0)))) (/ (* x 2.0) (- (* y z) (* t z)))) (FPCore (x y) :name "Linear.Projection:inversePerspective from linear-1.19.1.3, B" :alt (! :herbie-platform default (- (/ 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" :alt (! :herbie-platform default (+ (/ 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" :alt (! :herbie-platform default (/ 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" :alt (! :herbie-platform default (if (< x -1.7210442634149447e+81) (* (/ (* 2.0 x) (- x y)) y) (if (< x 8.364504563556443e+16) (/ (* x 2.0) (/ (- x y) y)) (* (/ (* 2.0 x) (- x y)) y)))) (/ (* (* x 2.0) y) (- x y))) (FPCore (x y) :name "Physics.ForceLayout:coulombForce from force-layout-0.4.0.2" :alt (! :herbie-platform default (/ (/ x y) y)) (/ x (* y y))) (FPCore (x y) :name "Codec.Picture.Types:toneMapping from JuicyPixels-3.2.6.1" :alt (! :herbie-platform default (* (/ 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" :alt (! :herbie-platform default (* 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 " (+ (+ (+ (+ x x) x) x) x)) (FPCore (x y z t) :name "Main:z from " :alt (! :herbie-platform default (+ (+ (+ (/ 1.0 (+ (sqrt (+ x 1.0)) (sqrt x))) (/ 1.0 (+ (sqrt (+ y 1.0)) (sqrt y)))) (/ 1.0 (+ (sqrt (+ z 1.0)) (sqrt z)))) (- (sqrt (+ t 1.0)) (sqrt t)))) (+ (+ (+ (- (sqrt (+ x 1.0)) (sqrt x)) (- (sqrt (+ y 1.0)) (sqrt y))) (- (sqrt (+ z 1.0)) (sqrt z))) (- (sqrt (+ t 1.0)) (sqrt t)))) (FPCore (x y z t a b c i) :name "Diagrams.ThreeD.Shapes:frustum from diagrams-lib-1.3.0.3, A" :alt (! :herbie-platform default (* 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" (+ x (* (- y x) z))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutY from diagrams-lib-1.3.0.3" (+ (* x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutX from diagrams-lib-1.3.0.3, A" (- (* x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Diagrams.ThreeD.Transform:aboutX from diagrams-lib-1.3.0.3, B" (+ (* x (sin y)) (* z (cos y)))) (FPCore (x y z) :name "Diagrams.Color.HSV:lerp from diagrams-contrib-1.3.0.5" :alt (! :herbie-platform default (- 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" :alt (! :herbie-platform default (if (< x -2.71483106713436e-162) (- (* (+ 1 y) (/ x z)) x) (if (< x 3.874108816439546e-197) (* (* x (+ (- y z) 1.0)) (/ 1 z)) (- (* (+ 1 y) (/ x z)) x)))) (/ (* x (+ (- y z) 1.0)) z)) (FPCore (x y) :name "Diagrams.Segment:$catParam from diagrams-lib-1.3.0.3, A" :alt (! :herbie-platform default (* (* 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" :alt (! :herbie-platform default (* (* x (* 3.0 y)) y)) (* (* (* x 3.0) y) y)) (FPCore (x) :name "Diagrams.Segment:$catParam from diagrams-lib-1.3.0.3, C" (* (* x x) x)) (FPCore (x) :name "Diagrams.Tangent:$catParam from diagrams-lib-1.3.0.3, D" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (- (* 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" (* (* x 3.0) x)) (FPCore (x y z t) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, A" :alt (! :herbie-platform default (/ (+ 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" :alt (! :herbie-platform default (if (< y -3693.8482788297247) (- (/ x (* y y)) (- (/ x y) x)) (if (< y 6799310503.41891) (/ (* x y) (+ y 1.0)) (- (/ 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" (/ (- x y) (- 1.0 y))) (FPCore (x y) :name "Diagrams.Trail:splitAtParam from diagrams-lib-1.3.0.3, D" :alt (! :herbie-platform default (if (< y -3693.8482788297247) (- (/ 1.0 y) (- (/ x y) x)) (if (< y 6799310503.41891) (- 1.0 (/ (* (- 1.0 x) y) (+ y 1.0))) (- (/ 1.0 y) (- (/ x y) x))))) (- 1.0 (/ (* (- 1.0 x) y) (+ y 1.0)))) (FPCore (x y) :name "Diagrams.TwoD.Arc:bezierFromSweepQ1 from diagrams-lib-1.3.0.3" :alt (! :herbie-platform default (* (/ (- 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" :alt (! :herbie-platform default (if (< (/ (- (* x x) (* (* y 4.0) y)) (+ (* x x) (* (* y 4.0) y))) 0.9743233849626781) (- (/ (* x x) (+ (* x x) (* (* y y) 4.0))) (/ (* (* y y) 4.0) (+ (* x x) (* (* y 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" (sqrt (- 1.0 (* x x)))) (FPCore (x y z t a) :name "Graphics.Rendering.Chart.Axis.Types:invLinMap from Chart-1.5.3" :alt (! :herbie-platform default (if (< z -1.2536131056095036e+188) (- t (* (/ y z) (- t x))) (if (< z 4.446702369113811e+64) (+ x (/ (- y z) (/ (- a z) (- t x)))) (- t (* (/ y z) (- t x)))))) (+ x (/ (* (- y z) (- t x)) (- a z)))) (FPCore (x y z) :name "Diagrams.TwoD.Segment:bezierClip from diagrams-lib-1.3.0.3" :alt (! :herbie-platform default (- z (* (- z x) y))) (+ (* x y) (* z (- 1.0 y)))) (FPCore (x y) :name "Data.Octree.Internal:octantDistance from Octree-0.5.4.2" :alt (! :herbie-platform default (if (< x -1.1236950826599826e+145) (- 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" (- x (* y y))) (FPCore (x y) :name "Diagrams.TwoD.Path.Metafont.Internal:hobbyF from diagrams-contrib-1.3.0.5" (/ (+ 2.0 (* (* (* (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)))))) (FPCore (x y) :name "Diagrams.TwoD.Layout.CirclePacking:approxRadius from diagrams-contrib-1.3.0.5" :alt (! :herbie-platform default (if (< y -1.2303690911306994e+114) 1.0 (if (< y -9.102852406811914e-222) (/ (sin (/ x (* y 2.0))) (* (sin (/ x (* y 2.0))) (log (exp (cos (/ x (* y 2.0))))))) 1.0))) (/ (tan (/ x (* y 2.0))) (sin (/ x (* y 2.0))))) (FPCore (x y z) :name "Diagrams.TwoD.Apollonian:descartes from diagrams-contrib-1.3.0.5" :alt (! :herbie-platform default (if (< z 7.636950090573675e+176) (* 2.0 (sqrt (+ (* (+ x y) z) (* x y)))) (* (* (+ (* 1/4 (* (* (pow y -3/4) (* (pow z -3/4) x)) (+ y z))) (* (pow z 1/4) (pow y 1/4))) (+ (* 1/4 (* (* (pow y -3/4) (* (pow z -3/4) x)) (+ y z))) (* (pow z 1/4) (pow y 1/4)))) 2.0))) (* 2.0 (sqrt (+ (+ (* x y) (* x z)) (* y z))))) (FPCore (x y z) :name "Diagrams.TwoD.Apollonian:initialConfig from diagrams-contrib-1.3.0.5, A" :alt (! :herbie-platform default (- (* 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" :alt (! :herbie-platform default (if (< y 2.5816096488251695e-278) (- (* x y)) (* x (* (sqrt (+ y z)) (sqrt (- y z)))))) (* x (sqrt (- (* y y) (* z z))))) (FPCore (x y z) :name "Diagrams.Backend.Rasterific:rasterificRadialGradient from diagrams-rasterific-1.3.1.3" :alt (! :herbie-platform default (- (+ 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" :alt (! :herbie-platform default (- (/ (+ (/ 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" (+ (* (+ (* x y) z) y) t)) (FPCore (x) :name "System.Random.MWC.Distributions:blocks from mwc-random-0.13.3.2" (* (* x 0.5) x)) (FPCore (x y) :name "System.Random.MWC.Distributions:standard from mwc-random-0.13.3.2" (* 0.5 (- (* x x) y))) (FPCore (x y z) :name "SynthBasics:oscSampleBasedAux from YampaSynth-0.2" (+ x (* y (- z x)))) (FPCore (x y z t) :name "System.Random.MWC.Distributions:truncatedExp from mwc-random-0.13.3.2" :alt (! :herbie-platform default (if (< z -2.8874623088207947e+119) (- (- 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" :alt (! :herbie-platform default (- (+ 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" (/ x (+ x y))) (FPCore (x) :name "Numeric.Integration.TanhSinh:nonNegative from integration-0.2.1" (/ x (- 1.0 x))) (FPCore (x y z) :name "Graphics.Rasterific.QuadraticFormula:discriminant from Rasterific-0.6.1" (- (* x x) (* (* y 4.0) z))) (FPCore (x y z t a b) :name "Graphics.Rasterific.CubicBezier:cachedBezierAt from Rasterific-0.6.1" :alt (! :herbie-platform default (if (< z -1.1820553527347888e+19) (+ (* z (+ (* b a) y)) (+ x (* t a))) (if (< z 4.7589743188364287e-122) (+ (* (+ (* b z) t) a) (+ (* z y) x)) (+ (* z (+ (* b a) y)) (+ x (* t a)))))) (+ (+ (+ x (* y z)) (* t a)) (* (* a z) b))) (FPCore (x) :name "Graphics.Rasterific.CubicBezier:isSufficientlyFlat from Rasterific-0.6.1" (* (* x 16.0) x)) (FPCore (x y z) :name "Graphics.Rasterific.Shading:$sgradientColorAt from Rasterific-0.6.1" :alt (! :herbie-platform default (- (/ x (- z y)) (/ y (- z y)))) (/ (- x y) (- z y))) (FPCore (x) :name "Graphics.Rasterific.Shading:$sradialGradientWithFocusShader from Rasterific-0.6.1, A" (+ (* x x) 1.0)) (FPCore (x y z t) :name "Graphics.Rasterific.Shading:$sradialGradientWithFocusShader from Rasterific-0.6.1, B" :alt (! :herbie-platform default (- (* 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" :alt (! :herbie-platform default (* 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" :alt (! :herbie-platform default (* (* (- (* 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" (- x (/ y (+ 1.0 (/ (* x y) 2.0))))) (FPCore (x y z t) :name "Numeric.AD.Rank1.Halley:findZero from ad-4.2.4" :alt (! :herbie-platform default (- 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" :alt (! :herbie-platform default (+ x (* y (/ y z)))) (+ x (/ (* y y) z))) (FPCore (x) :name "Numeric.Log:$cexpm1 from log-domain-0.10.2.1, A" :alt (! :herbie-platform default (* (* 2.0 x) x)) (* (* x 2.0) x)) (FPCore (x y) :name "Numeric.Log:$cexpm1 from log-domain-0.10.2.1, B" (+ (+ (* x y) x) y)) (FPCore (x y) :name "Numeric.Log:$clog1p from log-domain-0.10.2.1, A" :alt (! :herbie-platform default (+ (* 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" (/ x (+ 1.0 (sqrt (+ x 1.0))))) (FPCore (x) :name "Data.Approximate.Numerics:blog from approximate-0.2.2.1" :alt (! :herbie-platform default (/ 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" :alt (! :herbie-platform default (/ (/ (* 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" (- (+ x (cos y)) (* z (sin y)))) (FPCore (x y z) :name "Graphics.Rasterific.Svg.PathConverter:segmentToBezier from rasterific-svg-0.2.3.1, C" (+ (+ x (sin y)) (* z (cos y)))) (FPCore (x y z t) :name "Graphics.Rasterific.Svg.PathConverter:arcToSegments from rasterific-svg-0.2.3.1" :alt (! :herbie-platform default (+ (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" :alt (! :herbie-platform default 0) (* 1000000.0 (- x x))) (FPCore (x y z t a b) :name "Development.Shake.Progress:decay from shake-0.15.5" :alt (! :herbie-platform default (- (/ (+ (* 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" :alt (! :herbie-platform default (* (/ 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" (+ (* x y) (* (- 1.0 x) z))) (FPCore (x y z t) :name "Numeric.Histogram:binBounds from Chart-1.5.3" :alt (! :herbie-platform default (if (< x -9.025511195533005e-135) (- x (* (/ z t) (- x y))) (if (< x 4.275032163700715e-250) (+ x (* (/ (- y x) t) z)) (+ x (/ (- y x) (/ t z)))))) (+ x (/ (* (- y x) z) t))) (FPCore (x y z) :name "Graphics.Rendering.Chart.Drawing:drawTextsR from Chart-1.5.3" (+ (* x y) (* (- x 1.0) z))) (FPCore (x y) :name "Graphics.Rendering.Chart.Axis.Types:hBufferRect from Chart-1.5.3" :alt (! :herbie-platform default (- (* 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" :alt (! :herbie-platform default (if (< a -1.6153062845442575e-142) (+ x (* (/ (- y x) 1) (/ (- z t) (- a t)))) (if (< a 3.774403170083174e-182) (- y (* (/ z t) (- y x))) (+ x (* (/ (- y x) 1) (/ (- z t) (- a t))))))) (+ x (/ (* (- y x) (- z t)) (- a t)))) (FPCore (x y) :name "Graphics.Rendering.Chart.Plot.Vectors:renderPlotVectors from Chart-1.5.3" :alt (! :herbie-platform default (- (* 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" (+ x (/ (fabs (- y x)) 2.0))) (FPCore (x y z t) :name "Graphics.Rendering.Chart.Plot.AreaSpots:renderAreaSpots4D from Chart-1.5.3" :alt (! :herbie-platform default (/ 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" :alt (! :herbie-platform default (- y 0)) (- (+ x y) x)) (FPCore (x y z t a) :name "Graphics.Rendering.Chart.SparkLine:renderSparkLine from Chart-1.5.3" :alt (! :herbie-platform default (- 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" :alt (! :herbie-platform default (if (< y -3.7429310762689856e+171) (* (/ (+ y x) (- y)) z) (if (< y 3.5534662456086734e+168) (/ (+ x y) (- 1.0 (/ y z))) (* (/ (+ y x) (- y)) z)))) (/ (+ x y) (- 1.0 (/ y z)))) (FPCore (x y z t) :name "Graphics.Rendering.Chart.Backend.Diagrams:calcFontMetrics from Chart-diagrams-1.5.1, B" :alt (! :herbie-platform default (if (< (/ (* (/ y z) t) t) -1.20672205123045e+245) (/ y (/ z x)) (if (< (/ (* (/ y z) t) t) -5.907522236933906e-275) (* x (/ y z)) (if (< (/ (* (/ y z) t) t) 5.658954423153415e-65) (/ y (/ z x)) (if (< (/ (* (/ y z) t) t) 2.0087180502407133e+217) (* x (/ y z)) (/ (* y x) z)))))) (* x (/ (* (/ y z) t) t))) (FPCore (x y) :name "AI.Clustering.Hierarchical.Internal:average from clustering-0.2.1, B" (/ x (+ y x))) (FPCore (x y z t a b) :name "AI.Clustering.Hierarchical.Internal:ward from clustering-0.2.1" :alt (! :herbie-platform default (if (< (/ (- (+ (* (+ x y) z) (* (+ t y) a)) (* y b)) (+ (+ x t) y)) -3.5813117084150564e+153) (- (+ z a) b) (if (< (/ (- (+ (* (+ x y) z) (* (+ t y) a)) (* y b)) (+ (+ x t) y)) 1.2285964308315609e+82) (/ 1 (/ (+ (+ x t) y) (- (+ (* (+ x y) z) (* (+ t y) a)) (* y b)))) (- (+ z a) b)))) (/ (- (+ (* (+ x y) z) (* (+ t y) a)) (* y b)) (+ (+ x t) y))) (FPCore (x y z) :name "Numeric.SpecFunctions:invErfc from math-functions-0.1.5.2, A" :alt (! :herbie-platform default (+ 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" (* 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" (+ (- (* 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" :alt (! :herbie-platform default (if (< z -8120153.652456675) (- (* (+ (/ 0.07512208616047561 z) 0.0692910599291889) y) (- (/ (* 0.40462203869992125 y) (* z z)) x)) (if (< z 6.576118972787377e+20) (+ x (* (* y (+ (* (+ (* z 0.0692910599291889) 0.4917317610505968) z) 0.279195317918525)) (/ 1 (+ (* (+ z 6.012459259764103) z) 3.350343815022304)))) (- (* (+ (/ 0.07512208616047561 z) 0.0692910599291889) y) (- (/ (* 0.40462203869992125 y) (* z z)) x))))) (+ x (/ (* y (+ (* (+ (* z 0.0692910599291889) 0.4917317610505968) z) 0.279195317918525)) (+ (* (+ z 6.012459259764103) z) 3.350343815022304)))) (FPCore (x y z) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, C" :alt (! :herbie-platform default (if (< x -3.326128725870005e+62) (- (+ (/ y (* x x)) (* 4.16438922228 x)) 110.1139242984811) (if (< x 9.429991714554673e+55) (* (/ (- x 2.0) 1) (/ (+ (* (+ (* (+ (* (+ (* x 4.16438922228) 78.6994924154) x) 137.519416416) x) y) x) z) (+ (* (+ (+ (* 263.505074721 x) (+ (* 43.3400022514 (* x x)) (* x (* x x)))) 313.399215894) x) 47.066876606))) (- (+ (/ y (* x x)) (* 4.16438922228 x)) 110.1139242984811)))) (/ (* (- x 2.0) (+ (* (+ (* (+ (* (+ (* x 4.16438922228) 78.6994924154) x) 137.519416416) x) y) x) z)) (+ (* (+ (* (+ (* (+ x 43.3400022514) x) 263.505074721) x) 313.399215894) x) 47.066876606))) (FPCore (x y z t a b) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2, D" :alt (! :herbie-platform default (if (< z -6.499344996252632e+53) (+ x (* (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (* z z))) (/ y 1))) (if (< z 7.066965436914287e+59) (+ x (/ y (/ (+ (* (+ (* (+ (* (+ z 15.234687407) z) 31.4690115749) z) 11.9400905721) z) 0.607771387771) (+ (* (+ (* (+ (* (+ (* z 3.13060547623) 11.1667541262) z) t) z) a) z) b)))) (+ x (* (+ (- 3.13060547623 (/ 36.527041698806414 z)) (/ t (* z z))) (/ y 1)))))) (+ x (/ (* y (+ (* (+ (* (+ (* (+ (* z 3.13060547623) 11.1667541262) z) t) z) a) z) b)) (+ (* (+ (* (+ (* (+ z 15.234687407) z) 31.4690115749) z) 11.9400905721) z) 0.607771387771)))) (FPCore (x) :name "Numeric.SpecFunctions:$slogFactorial from math-functions-0.1.5.2, A" :alt (! :herbie-platform default (/ (/ 1.0 x) x)) (/ 1.0 (* x x))) (FPCore (x y z) :name "Numeric.SpecFunctions:$slogFactorial from math-functions-0.1.5.2, B" :alt (! :herbie-platform default (+ (+ (+ (* (- x 0.5) (log x)) (- 0.91893853320467 x)) (/ 0.083333333333333 x)) (* (/ z x) (- (* z (+ y 0.0007936500793651)) 0.0027777777777778)))) (+ (+ (- (* (- x 0.5) (log x)) x) 0.91893853320467) (/ (+ (* (- (* (+ y 0.0007936500793651) z) 0.0027777777777778) z) 0.083333333333333) x))) (FPCore (x y z t a) :name "Numeric.SpecFunctions:logGammaL from math-functions-0.1.5.2" :alt (! :herbie-platform default (+ (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" (- (* (* x x) 2.0) 1.0)) (FPCore (x y) :name "Numeric.SpecFunctions:log1p from math-functions-0.1.5.2, A" (* x (- 1.0 (* x y)))) ;; TODO: Trello Benchmark Sources - Numeric.SpecFunctions:log1p from math-functions-0.1.5.2, B ;; (FPCore (x) ;; :name "Numeric.SpecFunctions:log1p from math-functions-0.1.5.2, B" ;; (* 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" :alt (! :herbie-platform default (+ (+ (+ 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" (+ (+ (+ (+ (+ (* 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" :alt (! :herbie-platform default (/ x (/ z (+ y z)))) (/ (* x (+ y z)) z)) (FPCore (x y z) :name "Numeric.SpecFunctions:stirlingError from math-functions-0.1.5.2" :alt (! :herbie-platform default (- (- (+ 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" (+ (- (- (* x (log y)) y) z) (log t))) (FPCore (x y) :name "Numeric.SpecFunctions:incompleteGamma from math-functions-0.1.5.2, B" :alt (! :herbie-platform default (* 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" (- 1.0 (* x (+ 0.253 (* x 0.12))))) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteGamma from math-functions-0.1.5.2, B" :alt (! :herbie-platform default (if (< y -81284752.61947241) (- 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 (* 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" (- (/ (+ 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" :alt (! :herbie-platform default (- (- 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" :alt (! :herbie-platform default (/ (/ (/ 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" (* 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" :alt (! :herbie-platform default (if (< t -0.8845848504127471) (/ (* x (/ (pow a (- t 1.0)) y)) (- (+ b 1) (* y (log z)))) (if (< t 852031.2288374073) (/ (* (/ x y) (pow a (- t 1.0))) (exp (- b (* (log z) y)))) (/ (* x (/ (pow a (- t 1.0)) y)) (- (+ b 1) (* y (log z))))))) (/ (* x (exp (- (+ (* y (log z)) (* (- t 1.0) (log a))) b))) y)) (FPCore (x y z t) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, B" :alt (! :herbie-platform default (- (* (- z) (+ (+ (* 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" :alt (! :herbie-platform default (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) (+ (/ (* y x) z) (- (/ (* t x) (- 1.0 z)))) (* x (- (/ y z) (* t (/ 1 (- 1.0 z)))))))) (* x (- (/ y z) (/ t (- 1.0 z))))) (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, D" (- 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" (+ (- 1.0 x) (* y (sqrt x)))) (FPCore (x y) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, F" :alt (! :herbie-platform default (if (< y -3.7311844206647956e+94) (/ (exp (/ -1 y)) x) (if (< y 2.817959242728288e+37) (/ (pow (/ x (+ y x)) x) x) (if (< y 2.347387415166998e+178) (log (exp (/ (pow (/ x (+ y x)) x) x))) (/ (exp (/ -1 y)) x))))) (/ (exp (* x (log (/ x (+ x y))))) x)) (FPCore (x y z) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, G" :alt (! :herbie-platform default (if (< (/ y (+ z y)) 7.1154157597908e-315) (+ x (/ (exp (/ -1 z)) y)) (+ x (/ (exp (log (pow (/ y (+ y z)) y))) y)))) (+ x (/ (exp (* y (log (/ y (+ z y))))) y))) (FPCore (x) :name "Numeric.SpecFunctions:invIncompleteBetaWorker from math-functions-0.1.5.2, H" (/ (- (* 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" :alt (! :herbie-platform default (if (< t -2.118326644891581e-50) (/ x (+ x (* y (exp (* 2.0 (- (+ (* a c) (* 0.8333333333333334 c)) (* a b))))))) (if (< t 5.196588770651547e-123) (/ x (+ x (* y (exp (* 2.0 (/ (- (* (* z (sqrt (+ t a))) (* (* 3.0 t) (- a (/ 5.0 6.0)))) (* (- (* (+ (/ 5.0 6.0) a) (* 3.0 t)) 2.0) (* (- a (/ 5.0 6.0)) (* (- b c) t)))) (* (* (* t t) 3.0) (- a (/ 5.0 6.0))))))))) (/ x (+ x (* y (exp (* 2.0 (- (/ (* z (sqrt (+ t a))) t) (* (- b c) (- (+ a (/ 5.0 6.0)) (/ 2.0 (* t 3.0))))))))))))) (/ x (+ x (* y (exp (* 2.0 (- (/ (* z (sqrt (+ t a))) t) (* (- b c) (- (+ a (/ 5.0 6.0)) (/ 2.0 (* t 3.0))))))))))) (FPCore (x y z) :name "Numeric.SpecFunctions.Extra:bd0 from math-functions-0.1.5.2" :alt (! :herbie-platform default (if (< y 7.595077799083773e-308) (- (* x (log (/ x y))) z) (- (* x (- (log x) (log y))) z))) (- (* x (log (/ x y))) z)) (FPCore (x y z t a b c i) :name "Numeric.SpecFunctions:logGamma from math-functions-0.1.5.2" (/ (+ (* (+ (* (+ (* (+ (* 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" :alt (! :herbie-platform default (if (< z -3.1921305903852764e+46) (- (* y x)) (if (< z 5.976268120920894e+90) (/ (* 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" :alt (! :herbie-platform default (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" (* x (log x))) (FPCore (x) :name "Statistics.Correlation.Kendall:numOfTiesBy from math-functions-0.1.5.2" :alt (! :herbie-platform default (- (* x x) x)) (* x (- x 1.0))) (FPCore (x y z) :name "Statistics.Sample:robustSumVarWeighted from math-functions-0.1.5.2" (+ x (* (* y z) z))) (FPCore (x y z) :name "Statistics.Sample:$swelfordMean from math-functions-0.1.5.2" (+ x (/ (- y x) z))) (FPCore (x y) :name "Statistics.Sample:$skurtosis from math-functions-0.1.5.2" :alt (! :herbie-platform default (- (/ (/ 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" (+ (- (- 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" :alt (! :herbie-platform default (if (< z 249.6182814532307) (/ (* 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" (- (+ (* (- 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" (* (* x y) (- 1.0 y))) (FPCore (x y z) :name "Statistics.Distribution.Poisson:$clogProbability from math-functions-0.1.5.2" (- (- (* x (log y)) z) y)) (FPCore (x y z) :name "Statistics.Distribution.CauchyLorentz:$cdensity from math-functions-0.1.5.2" :alt (! :herbie-platform default (if (< (* y (+ 1.0 (* z z))) -inf.0) (/ (/ 1.0 y) (* (+ 1.0 (* z z)) x)) (if (< (* y (+ 1.0 (* z z))) 8.680743250567252e+305) (/ (/ 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" :alt (! :herbie-platform default (+ (* x x) (+ (* y y) (* 2 (* y x))))) (* (+ x y) (+ x y))) (FPCore (x y) :name "Examples.Basics.BasicTests:f2 from sbv-4.4" (- (* x x) (* y y))) (FPCore (x y) :name "Examples.Basics.BasicTests:f1 from sbv-4.4" (* (+ x y) (- x y))) (FPCore (x y) :name "Examples.Basics.ProofTests:f4 from sbv-4.4" :alt (! :herbie-platform default (+ (* 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" (/ (fabs (- x y)) (fabs y))) (FPCore (x y) :name "Data.Random.Distribution.Normal:normalF from random-fu-0.2.6.2" (exp (* (* x y) y))) (FPCore (x y) :name "Data.Random.Distribution.Normal:normalTail from random-fu-0.2.6.2" :alt (! :herbie-platform default (+ (+ y y) (* x x))) (+ (+ (* x x) y) y)) (FPCore (x) :name "Data.Random.Distribution.Normal:doubleStdNormalZ from random-fu-0.2.6.2" (- (+ x x) 1.0)) (FPCore (x y) :name "Data.Random.Distribution.T:$ccdf from random-fu-0.2.6.2" :alt (! :herbie-platform default (+ (* 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" (- 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" :alt (! :herbie-platform default (if (< (/ x (* (- y z) (- t z))) 0.0) (/ (/ x (- y z)) (- t z)) (* x (/ 1 (* (- y z) (- t z)))))) (/ x (* (- y z) (- t z)))) (FPCore (x) :name "Data.Random.Dice:roll from dice-0.1" (- (* x x) 1.0)) (FPCore (x) :name "Prelude:atanh from fay-base-0.20.0.1" (/ (+ x 1.0) (- 1.0 x))) (FPCore (x) :name "ReportTypes:explainFloat from gipeda-0.1.2.1" :alt (! :herbie-platform default 0) (* 100.0 (/ (- x x) x))) (FPCore (x y z t a) :name "Hakyll.Web.Tags:renderTagCloud from hakyll-4.7.2.3" (+ 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" (+ (+ (/ x 2.0) (* y x)) z)) (FPCore (x y) :name "Data.Histogram.Bin.LogBinD:$cbinSizeN from histogram-fill-0.8.4.1" (- (* x y) x)) (FPCore (x y z t a) :name "Numeric.Signal:interpolate from hsignal-0.2.7.1" (+ x (* (- y z) (/ (- t x) (- a z))))) (FPCore (x y z t) :name "Numeric.Signal.Multichannel:$cget from hsignal-0.2.7.1" :alt (! :herbie-platform default (if (< z 2.759456554562692e-282) (+ (* (/ x y) (- z t)) t) (if (< z 2.326994450874436e-110) (+ (* x (/ (- z t) y)) t) (+ (* (/ x y) (- z t)) t)))) (+ (* (/ x y) (- z t)) t)) (FPCore (x y z t) :name "Numeric.Signal.Multichannel:$cput from hsignal-0.2.7.1" :alt (! :herbie-platform default (/ t (/ (- z y) (- x y)))) (* (/ (- x y) (- z y)) t)) (FPCore (x y) :name "Data.HyperLogLog.Config:hll from hyperloglog-0.3.4" (* (* x y) y)) (FPCore (x y) :name "Data.HyperLogLog.Type:size from hyperloglog-0.3.4, A" :alt (! :herbie-platform default (* 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" :alt (! :herbie-platform default (if (< y 1.2973149052617803e-303) (* x (log (/ x y))) (/ x (/ 1 (- (log x) (log y)))))) (* x (log (/ x y)))) (FPCore (x y z) :name "Diagrams.Backend.Cairo.Internal:setTexture from diagrams-cairo-1.3.0.3" :alt (! :herbie-platform default (if (< z -2.060202331921739e+104) (- x (/ (* z x) y)) (if (< z 1.6939766013828526e+213) (/ x (/ y (- y z))) (* (- y z) (/ x y))))) (/ (* x (- y z)) y)) (FPCore (x y) :name "Numeric.Integration.TanhSinh:simpson from integration-0.2.1" (* x (+ y y))) (FPCore (x y) :name "Numeric.Integration.TanhSinh:everywhere from integration-0.2.1" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (+ 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" (+ (* 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" (+ (+ (+ (+ (+ 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" :alt (! :herbie-platform default (+ (* (+ 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" :alt (! :herbie-platform default (if (< (* (- y x) (/ z t)) -1013646692435.8867) (+ x (/ (- y x) (/ t z))) (if (< (* (- y x) (/ z t)) -0.0) (+ x (/ (* (- y x) z) t)) (+ x (/ (- y x) (/ t z)))))) (+ x (* (- y x) (/ z t)))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisLine from plot-0.2.3.4, A" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (if (< y -8.508084860551241e-17) (+ x (* y (/ (- z t) (- a t)))) (if (< y 2.894426862792089e-49) (+ x (* (* y (- z t)) (/ 1 (- a t)))) (+ x (* y (/ (- z t) (- a t))))))) (+ x (* y (/ (- z t) (- a t))))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTick from plot-0.2.3.4, A" :alt (! :herbie-platform default (if (< t -1.0682974490174067e-39) (+ x (* (/ (- y z) (- a z)) t)) (if (< t 3.9110949887586375e-141) (+ x (/ (* (- y z) t) (- a z))) (+ x (* (/ (- y z) (- a z)) t))))) (+ x (/ (* (- y z) t) (- a z)))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTick from plot-0.2.3.4, B" :alt (! :herbie-platform default (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) (/ (- (* y (- a z)) (* x t)) (- a t)) (- (+ y x) (* (* (- z t) (/ 1 (- a t))) y))))) (- (+ x y) (/ (* (- z t) y) (- a t)))) (FPCore (x y z t a) :name "Graphics.Rendering.Plot.Render.Plot.Axis:renderAxisTicks from plot-0.2.3.4, A" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (+ 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" :alt (! :herbie-platform default (if (< z -6.396479394109776e+136) (/ (- z) (sqrt 3.0)) (if (< z 7.320293694404182e+117) (/ (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" (+ 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" :alt (! :herbie-platform default (- (* 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" (+ 1.0 (/ (* 4.0 (- (+ x (* y 0.25)) z)) y))) (FPCore (x) :name "Data.Spline.Key:interpolateKeys from smoothie-0.4.0.2" :alt (! :herbie-platform default (* 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" :alt (! :herbie-platform default (if (< z -6.396479394109776e+136) (- 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" :alt (! :herbie-platform default (+ x (* y (* z (- (tanh (/ t y)) (tanh (/ x y))))))) (+ x (* (* y z) (- (tanh (/ t y)) (tanh (/ x y)))))) ================================================ FILE: bench/libraries/fast-math.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (d1 d2 d3) :name "FastMath dist" :alt (! :herbie-platform c (* d1 (+ d2 d3))) (+ (* d1 d2) (* d1 d3))) (FPCore (d) :name "FastMath test1" :alt (! :herbie-platform c (* d 30)) (+ (* d 10) (* d 20))) (FPCore (d1 d2) :name "FastMath test2" :alt (! :herbie-platform c (* d1 (+ 30 d2))) (+ (+ (* d1 10) (* d1 d2)) (* d1 20))) (FPCore (d1 d2 d3) :name "FastMath dist3" :alt (! :herbie-platform c (* d1 (+ 37 d3 d2))) (+ (+ (* d1 d2) (* (+ d3 5) d1)) (* d1 32))) (FPCore (d1 d2 d3 d4) :name "FastMath dist4" :alt (! :herbie-platform c (* d1 (- (+ (- d2 d3) d4) d1))) (- (+ (- (* d1 d2) (* d1 d3)) (* d4 d1)) (* d1 d1))) (FPCore (d1 d2 d3) :name "FastMath test3" :alt (! :herbie-platform c (* d1 (+ 3 d2 d3))) (+ (+ (* d1 3) (* d1 d2)) (* d1 d3))) (FPCore (d1) :name "FastMath repmul" :alt (! :herbie-platform c (pow d1 4)) (* (* (* d1 d1) d1) d1)) (FPCore (d1) :name "FastMath test5" :alt (! :herbie-platform c (pow d1 10)) (* (* d1 (* (* (* (* (* d1 (* d1 d1)) d1) d1) (* d1 d1)) d1)) d1)) ================================================ FILE: bench/libraries/jmatjs.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (z) :name "Jmat.Real.gamma, branch z greater than 0.5" :pre (> z 0.5) (let ((z* (- z 1)) (g 7)) (let ((x (+ (+ (+ (+ (+ (+ (+ (+ 0.9999999999998099 (/ 676.5203681218851 (+ z* 1))) (/ -1259.1392167224028 (+ z* 2))) (/ 771.3234287776531 (+ z* 3))) (/ -176.6150291621406 (+ z* 4))) (/ 12.507343278686905 (+ z* 5))) (/ -0.13857109526572012 (+ z* 6))) (/ 9.984369578019572e-06 (+ z* 7))) (/ 1.5056327351493116e-07 (+ z* 8)))) (t (+ (+ z* g) 0.5))) (* (* (* (sqrt (* PI 2)) (pow t (+ z* 0.5))) (exp (- t))) x)))) (FPCore (z) :name "Jmat.Real.gamma, branch z less than 0.5" :pre (<= z 0.5) (let ((z* (- (- 1 z) 1)) (g 7)) (let ((x (+ (+ (+ (+ (+ (+ (+ (+ 0.9999999999998099 (/ 676.5203681218851 (+ z* 1))) (/ -1259.1392167224028 (+ z* 2))) (/ 771.3234287776531 (+ z* 3))) (/ -176.6150291621406 (+ z* 4))) (/ 12.507343278686905 (+ z* 5))) (/ -0.13857109526572012 (+ z* 6))) (/ 9.984369578019572e-06 (+ z* 7))) (/ 1.5056327351493116e-07 (+ z* 8)))) (t (+ (+ z* g) 0.5))) (* (/ PI (sin (* PI z))) (* (* (* (sqrt (* PI 2)) (pow t (+ z* 0.5))) (exp (- t))) x))))) (FPCore (x) :name "Jmat.Real.lambertw, estimator" (- (log x) (log (log x)))) (FPCore (wj x) :name "Jmat.Real.lambertw, newton loop step" :alt (! :herbie-platform c (let ((ew (exp wj))) (- wj (- (/ wj (+ wj 1)) (/ x (+ ew (* wj ew))))))) (let ((ew (exp wj))) (- wj (/ (- (* wj ew) x) (+ ew (* wj ew)))))) (FPCore (x) :name "Jmat.Real.dawson" (let ((p1 0.1049934947) (p2 0.0424060604) (p3 0.0072644182) (p4 0.0005064034) (p5 0.0001789971) (q1 0.7715471019) (q2 0.2909738639) (q3 0.0694555761) (q4 0.0140005442) (q5 0.0008327945)) (let ((x2 (* x x))) (let ((x4 (* x2 x2))) (let ((x6 (* x4 x2))) (let ((x8 (* x6 x2))) (let ((x10 (* x8 x2))) (let ((x12 (* x10 x2))) (* (/ (+ (+ (+ (+ (+ 1 (* p1 x2)) (* p2 x4)) (* p3 x6)) (* p4 x8)) (* p5 x10)) (+ (+ (+ (+ (+ (+ 1 (* q1 x2)) (* q2 x4)) (* q3 x6)) (* q4 x8)) (* q5 x10)) (* (* 2 p5) x12))) x))))))))) (FPCore (x) :name "Jmat.Real.erfi, branch x less than or equal to 0.5" :pre (<= x 0.5) (let ((sqrtPI (sqrt PI))) (let ((ps (/ 1 sqrtPI))) (let ((x* (fabs x))) (let ((x3 (* (* x* x*) x*))) (let ((x5 (* (* x3 x*) x*))) (let ((x7 (* (* x5 x*) x*))) (let ((t (+ (+ (+ (* 2 x*) (* (/ 2 3) x3)) (* (/ 1 5) x5)) (* (/ 1 21) x7)))) (fabs (* ps t)))))))))) (FPCore (x) :name "Jmat.Real.erfi, branch x greater than or equal to 5" :pre (>= x 0.5) (let ((sqrtPI (sqrt PI))) (let ((ps (/ 1 sqrtPI))) (let ((x* (fabs x))) (let ((xi (/ 1 x*))) (let ((xi3 (* (* xi xi) xi))) (let ((xi5 (* (* xi3 xi) xi))) (let ((xi7 (* (* xi5 xi) xi))) (let ((e (exp (* x* x*)))) (let ((t (+ (+ (+ xi (* (/ 1 2) xi3)) (* (/ 3 4) xi5)) (* (/ 15 8) xi7)))) (* (* ps e) t))))))))))) (FPCore (x) :name "Jmat.Real.erf" (let ((x* (fabs x))) (let ((t (/ 1 (+ 1 (* 0.3275911 x*))))) (let ((p (* t (+ 0.254829592 (* t (+ -0.284496736 (* t (+ 1.421413741 (* t (+ -1.453152027 (* t 1.061405429))))))))))) (- 1 (* p (exp (- (* x* x*))))))))) ================================================ FILE: bench/libraries/mathjs/arithmetic.fpcore ================================================ ; -*- mode: scheme -*- (FPCore modulus (re im) :name "math.abs on complex" (sqrt (+ (* re re) (* im im)))) (FPCore modulus_sqr (re im) :name "math.abs on complex (squared)" (+ (* re re) (* im im))) (FPCore re_sqr (re im) :name "math.square on complex, real part" (- (* re re) (* im im))) (FPCore im_sqr (re im) :name "math.square on complex, imaginary part" (+ (* re im) (* im re))) (FPCore (x) :name "math.cube on real" :alt (! :herbie-platform c (pow x 3)) (* (* x x) x)) (FPCore (x.re x.im) :name "math.cube on complex, real part" :alt (! :herbie-platform c (+ (* (* x.re x.re) (- x.re x.im)) (* (* x.re x.im) (- x.re (* 3 x.im))))) (- (* (re_sqr x.re x.im) x.re) (* (im_sqr x.re x.im) x.im))) (FPCore (x.re x.im) :name "math.cube on complex, imaginary part" :alt (! :herbie-platform c (+ (* (* x.re x.im) (* 2 x.re)) (* (* x.im (- x.re x.im)) (+ x.re x.im)))) (+ (* (re_sqr x.re x.im) x.im) (* (im_sqr x.re x.im) x.re))) (FPCore (x.re x.im y.re y.im) :name "_divideComplex, real part" (/ (+ (* x.re y.re) (* x.im y.im)) (modulus_sqr y.re y.im))) (FPCore (x.re x.im y.re y.im) :name "_divideComplex, imaginary part" (/ (- (* x.im y.re) (* x.re y.im)) (modulus_sqr y.re y.im))) (FPCore (re im) :name "math.exp on complex, real part" (* (exp re) (cos im))) (FPCore (re im) :name "math.exp on complex, imaginary part" (* (exp re) (sin im))) (FPCore (re im) :name "math.log/1 on complex, real part" (log (modulus re im))) (FPCore (re im) :name "math.log/1 on complex, imaginary part" (atan2 im re)) (FPCore (re im base) :name "math.log/2 on complex, real part" (/ (+ (* (log (modulus re im)) (log base)) (* (atan2 im re) 0)) (+ (* (log base) (log base)) (* 0 0)))) (FPCore (re im base) :name "math.log/2 on complex, imaginary part" (/ (- (* (atan2 im re) (log base)) (* (log (modulus re im)) 0)) (+ (* (log base) (log base)) (* 0 0)))) (FPCore (re im) :name "math.log10 on complex, real part" (/ (log (modulus re im)) (log 10))) (FPCore (re im) :name "math.log10 on complex, imaginary part" (/ (atan2 im re) (log 10))) (FPCore (x.re x.im y.re y.im) :name "_multiplyComplex, real part" (- (* x.re y.re) (* x.im y.im))) (FPCore (x.re x.im y.re y.im) :name "_multiplyComplex, imaginary part" (+ (* x.re y.im) (* x.im y.re))) (FPCore (x.re x.im y.re y.im) :name "powComplex, real part" (* (exp (- (* (log (modulus x.re x.im)) y.re) (* (atan2 x.im x.re) y.im))) (cos (+ (* (log (modulus x.re x.im)) y.im) (* (atan2 x.im x.re) y.re))))) (FPCore (x.re x.im y.re y.im) :name "powComplex, imaginary part" (* (exp (- (* (log (modulus x.re x.im)) y.re) (* (atan2 x.im x.re) y.im))) (sin (+ (* (log (modulus x.re x.im)) y.im) (* (atan2 x.im x.re) y.re))))) (FPCore (re im) :name "math.sqrt on complex, real part" :alt (! :herbie-platform c (if (< re 0) (* 0.5 (* (sqrt 2) (sqrt (/ (* im im) (- (modulus re im) re))))) (* 0.5 (sqrt (* 2.0 (+ (modulus re im) re)))))) (* 0.5 (sqrt (* 2.0 (+ (modulus re im) re))))) (FPCore (re im) :name "math.sqrt on complex, imaginary part, im greater than 0 branch" :pre (> im 0) (* 0.5 (sqrt (* 2.0 (- (modulus re im) re))))) ================================================ FILE: bench/libraries/mathjs/complex.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (re im) :name "math.arg on complex" (atan2 im re)) ================================================ FILE: bench/libraries/mathjs/probability.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (u1 u2) :name "normal distribution" :pre (and (<= 0 u1 1) (<= 0 u2 1)) (+ (* (* (/ 1 6) (pow (* -2 (log u1)) 0.5)) (cos (* (* 2 PI) u2))) 0.5)) ================================================ FILE: bench/libraries/mathjs/trigonometry.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (re im) :name "math.cos on complex, real part" (* (* 0.5 (cos re)) (+ (exp (- im)) (exp im)))) (FPCore (re im) :name "math.cos on complex, imaginary part" :alt (! :herbie-platform default (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))))) (* (* 0.5 (sin re)) (- (exp (- im)) (exp im)))) (FPCore (re im) :name "math.sin on complex, real part" (* (* 0.5 (sin re)) (+ (exp (- 0 im)) (exp im)))) (FPCore (re im) :name "math.sin on complex, imaginary part" :alt (! :herbie-platform default (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))))) (* (* 0.5 (cos re)) (- (exp (- 0 im)) (exp im)))) (FPCore (x) :name "Ian Simplification" :alt (! :herbie-platform default (asin x)) (- (/ PI 2) (* 2 (asin (sqrt (/ (- 1 x) 2)))))) ================================================ FILE: bench/libraries/octave/CollocWt.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (alpha beta) :pre (and (> alpha -1) (> beta -1)) :name "Octave 3.8, jcobi/1" (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (/ (+ (/ ad (+ ab 2.0)) 1.0) 2.0))) ; TODO: i should be an integer (FPCore (alpha beta i) :pre (and (> alpha -1) (> beta -1) (> i 0)) :name "Octave 3.8, jcobi/2" (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z (+ ab (* 2 i)))) (/ (+ (/ (/ (* ab ad) z) (+ z 2.0)) 1.0) 2.0)))) (FPCore (alpha beta) :pre (and (> alpha -1) (> beta -1)) :name "Octave 3.8, jcobi/3" (let ((i 1) (ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z1 i)) (let ((z (+ ab (* 2 z1)))) (/ (/ (/ (+ (+ ab ap) 1.0) z) z) (+ z 1.0)))))) (FPCore (alpha beta i) ; TODO: i should be an integer :pre (and (> alpha -1) (> beta -1) (> i 1)) :name "Octave 3.8, jcobi/4" (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z (+ ab (* 2 i)))) (let ((z* (* z z)) (y (* i (+ ab i)))) (let ((y* (* y (+ ap y)))) (/ (/ y* z*) (- z* 1.0))))))) ; TODO: i should be an integer (FPCore (i) :pre (and (> i 0)) :name "Octave 3.8, jcobi/4, as called" (let ((z (* 2 i))) (let ((z* (* z z)) (y (* i i))) (let ((y* (* y y))) (/ (/ y* z*) (- z* 1.0)))))) ================================================ FILE: bench/libraries/octave/randgamma.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a rand) :name "Octave 3.8, oct_fill_randg" (let ((d (- a (/ 1.0 3.0)))) (let ((c (/ 1 (sqrt (* 9 d)))) (x rand)) (let ((v (+ 1 (* c x)))) (let ((v* (* v (* v v))) (xsq (* x x))) (* d v)))))) ================================================ FILE: bench/libraries/rust.fpcore ================================================ (FPCore (x) :name "Rust f64::asinh" :spec (asinh x) :alt (! :herbie-platform c (let* ([ax (fabs x)] [ix (/ 1 ax)]) (copysign (log1p (+ ax (/ ax (+ (hypot 1 ix) ix)))) x))) (copysign (log (+ (fabs x) (sqrt (+ (* x x) 1)))) x)) (FPCore (x) :name "Rust f32::asinh" :spec (asinh x) :precision binary32 :alt (! :herbie-platform c (let* ([ax (fabs x)] [ix (/ 1 ax)]) (copysign (log1p (+ ax (/ ax (+ (hypot 1 ix) ix)))) x))) (copysign (log (+ (fabs x) (sqrt (+ (* x x) 1)))) x)) (FPCore (x) :name "Rust f64::acosh" :spec (acosh x) :pre (>= x 1) :alt (! :herbie-platform c (log (+ x (* (sqrt (- x 1)) (sqrt (+ x 1)))))) (log (+ x (sqrt (- (* x x) 1))))) (FPCore (x) :name "Rust f32::acosh" :spec (acosh x) :precision binary32 :pre (>= x 1) :alt (! :herbie-platform c (log (+ x (* (sqrt (- x 1)) (sqrt (+ x 1)))))) (log (+ x (sqrt (- (* x x) 1))))) (FPCore (x) :name "Rust f64::atanh" :spec (atanh x) (* 0.5 (log1p (/ (* 2 x) (- 1 x))))) (FPCore (x) :name "Rust f32::atanh" :spec (atanh x) :precision binary32 (* 0.5 (log1p (/ (* 2 x) (- 1 x))))) ================================================ FILE: bench/mathematics/arvind.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a b) :name "Exp of sum of logs" :alt (! :herbie-platform c (* a b)) (exp (+ (log a) (log b)))) (FPCore (a b) :name "Quotient of sum of exps" :alt (! :herbie-platform c (/ 1 (+ 1 (exp (- b a))))) (/ (exp a) (+ (exp a) (exp b)))) (FPCore (a1 a2 b1 b2) :name "Quotient of products" :alt (! :herbie-platform c (* (/ a1 b1) (/ a2 b2))) (/ (* a1 a2) (* b1 b2))) ================================================ FILE: bench/mathematics/beta-distribution.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (m v) :pre (and (< 0 m) (< 0 v) (< v 0.25)) :name "a parameter of renormalized beta distribution" (* (- (/ (* m (- 1 m)) v) 1) m)) (FPCore (m v) :pre (and (< 0 m) (< 0 v) (< v 0.25)) :name "b parameter of renormalized beta distribution" (* (- (/ (* m (- 1 m)) v) 1) (- 1 m))) ================================================ FILE: bench/mathematics/dirichlet-mixture-model.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (c_p c_n t s) :pre (and (< 0 c_p) (< 0 c_n)) :name "Harley's example" :alt (! :herbie-platform c (* (pow (/ (+ 1 (exp (- t))) (+ 1 (exp (- s)))) c_p) (pow (/ (+ 1 (exp t)) (+ 1 (exp s))) c_n))) (/ (* (pow (/ 1 (+ 1 (exp (- s)))) c_p) (pow (- 1 (/ 1 (+ 1 (exp (- s))))) c_n)) (* (pow (/ 1 (+ 1 (exp (- t)))) c_p) (pow (- 1 (/ 1 (+ 1 (exp (- t))))) c_n)))) ================================================ FILE: bench/mathematics/excel.fpcore ================================================ (FPCore (x0 x1) :name "(- (/ x0 (- 1 x1)) x0)" :pre (or (and (== x0 1.855) (== x1 0.000209)) (and (== x0 2.985) (== x1 0.0186))) :alt (! :herbie-platform c (/ (* x0 x1) (- 1 x1))) (- (/ x0 (- 1 x1)) x0)) ================================================ FILE: bench/mathematics/gui.fpcore ================================================ ;; from racket/gui https://github.com/racket/gui/commit/e8decf79852b9dac06ebd91a9aae5c0c3b215c34 ;; Robby simplifies this to just (/ (* 4 F) (pow (* x-scale y-scale) 2)) (FPCore (a b angle x-scale y-scale) :name "Simplification of discriminant from scale-rotated-ellipse" (let* ([θ (* (/ angle 180) PI)] [F (* (* b a) (* b (- a)))] [A (/ (/ (+ (pow (* a (sin θ)) 2) (pow (* b (cos θ)) 2)) x-scale) x-scale)] [B (/ (/ (* (* (* 2 (- (pow b 2) (pow a 2))) (sin θ)) (cos θ)) x-scale) y-scale)] [C (/ (/ (+ (pow (* a (cos θ)) 2) (pow (* b (sin θ)) 2)) y-scale) y-scale)]) (- (* B B) (* (* 4 A) C)))) (FPCore (a b angle x-scale y-scale) :name "raw-angle from scale-rotated-ellipse" (let* ([θ (* (/ angle 180) PI)] [F (* (* b a) (* b (- a)))] [A (/ (/ (+ (pow (* a (sin θ)) 2) (pow (* b (cos θ)) 2)) x-scale) x-scale)] [B (/ (/ (* (* (* 2 (- (pow b 2) (pow a 2))) (sin θ)) (cos θ)) x-scale) y-scale)] [C (/ (/ (+ (pow (* a (cos θ)) 2) (pow (* b (sin θ)) 2)) y-scale) y-scale)] [B^2-4AC (/ (* 4 F) (pow (* x-scale y-scale) 2))] [q (* (* 2 B^2-4AC) F)] [r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (* 180 (/ (atan (/ (- (- C A) r) B)) PI)))) (FPCore (a b angle x-scale y-scale) :name "a from scale-rotated-ellipse" (let* ([θ (* (/ angle 180) PI)] [F (* (* b a) (* b (- a)))] [A (/ (/ (+ (pow (* a (sin θ)) 2) (pow (* b (cos θ)) 2)) x-scale) x-scale)] [B (/ (/ (* (* (* 2 (- (pow b 2) (pow a 2))) (sin θ)) (cos θ)) x-scale) y-scale)] [C (/ (/ (+ (pow (* a (cos θ)) 2) (pow (* b (sin θ)) 2)) y-scale) y-scale)] [B^2-4AC (/ (* 4 F) (pow (* x-scale y-scale) 2))] [q (* (* 2 B^2-4AC) F)] [r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (/ (- (sqrt (* q (+ (+ A C) r)))) B^2-4AC))) (FPCore (a b angle x-scale y-scale) :name "b from scale-rotated-ellipse" (let* ([θ (* (/ angle 180) PI)] [F (* (* b a) (* b (- a)))] [A (/ (/ (+ (pow (* a (sin θ)) 2) (pow (* b (cos θ)) 2)) x-scale) x-scale)] [B (/ (/ (* (* (* 2 (- (pow b 2) (pow a 2))) (sin θ)) (cos θ)) x-scale) y-scale)] [C (/ (/ (+ (pow (* a (cos θ)) 2) (pow (* b (sin θ)) 2)) y-scale) y-scale)] [B^2-4AC (/ (* 4 F) (pow (* x-scale y-scale) 2))] [q (* (* 2 B^2-4AC) F)] [r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (/ (- (sqrt (* q (- (+ A C) r)))) B^2-4AC))) ;; the following 7 are broken-down versions of the three above (FPCore (a b angle) :name "ab-angle->ABCF A" (+ (pow (* a (sin (* (/ angle 180) PI))) 2) (pow (* b (cos (* (/ angle 180) PI))) 2))) (FPCore (a b angle) :name "ab-angle->ABCF B" (* (* (* 2 (- (pow b 2) (pow a 2))) (sin (* PI (/ angle 180)))) (cos (* PI (/ angle 180))))) (FPCore (a b angle) :name "ab-angle->ABCF C" (+ (pow (* a (cos (* PI (/ angle 180)))) 2) (pow (* b (sin (* PI (/ angle 180)))) 2))) (FPCore (a b) :name "ab-angle->ABCF D" (- (* (* (* a a) b) b))) (FPCore (A B C) :name "ABCF->ab-angle angle" (let ([r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (* 180 (/ (atan (* (/ 1 B) (- (- C A) r))) PI)))) (FPCore (A B C F) :name "ABCF->ab-angle a" (let* ([B2-4AC (- (pow B 2) (* (* 4 A) C))] [q (* 2 (* B2-4AC F))] [r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (/ (- (sqrt (* q (+ (+ A C) r)))) B2-4AC))) (FPCore (A B C F) :name "ABCF->ab-angle b" (let* ([B2-4AC (- (pow B 2) (* (* 4 A) C))] [q (* 2 (* B2-4AC F))] [r (sqrt (+ (pow (- A C) 2) (pow B 2)))]) (/ (- (sqrt (* q (- (+ A C) r)))) B2-4AC))) (FPCore (eh ew t ) :name "Example from Robby" (let ([t1 (atan (/ (/ eh ew) (tan t)))]) (fabs (+ (* (* ew (sin t)) (cos t1)) (* (* eh (cos t)) (sin t1)))))) (FPCore (eh ew t) :name "Example 2 from Robby" (let ([t2 (atan (/ (* (- eh) (tan t)) ew))]) (fabs (- (* (* ew (cos t)) (cos t2)) (* (* eh (sin t)) (sin t2)))))) ================================================ FILE: bench/mathematics/hyperbolic-functions.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "Hyperbolic sine" (/ (- (exp x) (exp (- x))) 2)) (FPCore (x) :name "Hyperbolic tangent" (/ (- (exp x) (exp (- x))) (+ (exp x) (exp (- x))))) (FPCore (x) :name "Hyperbolic secant" (/ 2 (+ (exp x) (exp (- x))))) (FPCore (x) :name "Hyperbolic arcsine" :alt (! :herbie-platform c (if (< x 0) (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 (- (* x x) 1))))) (FPCore (x) :name "Hyperbolic arc-(co)tangent" (* (/ 1 2) (log (/ (+ 1 x) (- 1 x))))) (FPCore (x) :name "Hyperbolic arc-(co)secant" (log (+ (/ 1 x) (/ (sqrt (- 1 (* x x))) x)))) ================================================ FILE: bench/mathematics/latlong.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (R lambda1 lambda2 phi1 phi2) :name "Distance on a great circle" (let ((dlambda (- lambda1 lambda2))) (let ((dphi (- phi1 phi2))) (let ((a (+ (pow (sin (/ dphi 2)) 2) (* (* (* (cos phi1) (cos phi2)) (sin (/ dlambda 2))) (sin (/ dlambda 2)))))) (let ((c (* 2 (atan2 (sqrt a) (sqrt (- 1 a)))))) (let ((d (* R c))) d)))))) (FPCore (R lambda1 lambda2 phi1 phi2) :name "Spherical law of cosines" (* (acos (+ (* (sin phi1) (sin phi2)) (* (* (cos phi1) (cos phi2)) (cos (- lambda1 lambda2))))) R)) (FPCore (R lambda1 lambda2 phi1 phi2) :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 (+ (* x x) (* y y)))))) d)))) (FPCore (lambda1 lambda2 phi1 phi2) :name "Bearing on a great circle" (atan2 (* (sin (- lambda1 lambda2)) (cos phi2)) (- (* (cos phi1) (sin phi2)) (* (* (sin phi1) (cos phi2)) (cos (- lambda1 lambda2)))))) (FPCore (lambda1 lambda2 phi1 phi2) :name "Midpoint on a great circle" (let ((dlambda (- lambda1 lambda2))) (let ((Bx (* (cos phi2) (cos dlambda))) (By (* (cos phi2) (sin dlambda)))) (let ((phim (atan2 (+ (sin phi1) (sin phi2)) (sqrt (+ (pow (+ (cos phi1) Bx) 2) (* By By))))) (lambdam (+ lambda1 (atan2 By (+ (cos phi1) Bx))))) lambdam)))) ;; TODO: phi2 unused (FPCore (lambda1 phi1 phi2 delta theta) :name "Destination given bearing on a great circle" (let ((phi2 (asin (+ (* (sin phi1) (cos delta)) (* (* (cos phi1) (sin delta)) (cos theta)))))) (let ((lambda2 (+ lambda1 (atan2 (* (* (sin theta) (sin delta)) (cos phi1)) (- (cos delta) (* (sin phi1) (sin phi2))))))) lambda2))) ================================================ FILE: bench/mathematics/logistic-regression.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x y) :name "Logistic regression 2" :alt (! :herbie-platform c (if (<= x 0) (- (log (+ 1 (exp x))) (* x y)) (- (log (+ 1 (exp (- x)))) (* (- x) (- 1 y))))) (- (log (+ 1 (exp x))) (* x y))) (FPCore (x) :name "Logistic function from Lakshay Garg" (- (/ 2 (+ 1 (exp (* -2 x)))) 1)) ================================================ FILE: bench/mathematics/sarnoff.fpcore ================================================ ;; 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) :name "Cubic critical" (/ (+ (- b) (sqrt (- (* b b) (* (* 3 a) c)))) (* 3 a))) (FPCore (a b c) :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) :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) :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))))) ================================================ FILE: bench/mathematics/statistics.fpcore ================================================ (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))))) ================================================ FILE: bench/mathematics/symmetry.fpcore ================================================ (FPCore (a b) :name "symmetry log of sum of exp" (log (+ (exp a) (exp b)))) ================================================ FILE: bench/mathematics/xkcd-expr.fpcore ================================================ (FPCore () :name "xkcd217 (a numerical coincidence)" :precision binary64 (- 20 (- (exp PI) PI))) ================================================ FILE: bench/numerics/conte.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "ENA, Section 1.4, Mentioned, A" :cite (conte-et-al-1980) :pre (<= -0.01 x 0.01) :alt (! :herbie-platform c (/ (* (sin x) (sin x)) (+ 1 (cos x)))) (- 1 (cos x))) (FPCore (x) :name "ENA, Section 1.4, Mentioned, B" :cite (conte-et-al-1980) :pre (<= 0.999 x 1.001) (/ 10 (- 1 (* x x)))) (FPCore (x) :name "ENA, Section 1.4, Exercise 4a" :cite (conte-et-al-1980) :pre (<= -1 x 1) :alt (! :herbie-platform c (* 1/6 (* x x))) (/ (- x (sin x)) (tan x))) (FPCore (x) :name "ENA, Section 1.4, Exercise 1" :cite (conte-et-al-1980) :pre (<= 1.99 x 2.01) (* (cos x) (exp (* 10 (* x x))))) (FPCore (x eps) :name "ENA, Section 1.4, Exercise 4b, n=2" :cite (conte-et-al-1980) :pre (and (<= -1e9 x 1e9) (<= -1 eps 1)) (- (pow (+ x eps) 2) (pow x 2))) (FPCore (x eps) :name "ENA, Section 1.4, Exercise 4b, n=5" :cite (conte-et-al-1980) :pre (and (<= -1e9 x 1e9) (<= -1 eps 1)) (- (pow (+ x eps) 5) (pow x 5))) (FPCore (x eps) :name "ENA, Section 1.4, Exercise 4d" :cite (conte-et-al-1980) :pre (and (<= 0 x 1e9) (<= -1 eps 1)) :alt (! :herbie-platform c (/ eps (+ x (sqrt (- (* x x) eps))))) (- x (sqrt (- (* x x) eps)))) ================================================ FILE: bench/numerics/every-cs.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a b c) :name "The quadratic formula (r1)" :alt (! :herbie-platform c (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 (- (* b b) (* (* 4 a) c)))) (/ (+ (- b) (sqrt d)) (* 2 a)))) (FPCore (a b c) :name "The quadratic formula (r2)" :alt (! :herbie-platform c (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 (- (* b b) (* 4 (* a c)))))) (/ (- (- b) d) (* 2 a)))) (FPCore (a b) :name "Difference of squares" :alt (! :herbie-platform c (* (+ a b) (- a b))) (- (* a a) (* b b))) (FPCore (a b c) :name "Area of a triangle" :pre (and (< 0 a (+ b c)) (< 0 b (+ a c)) (< 0 c (+ a b))) :alt (! :herbie-platform c (/ (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)" :alt (! :herbie-platform c (if (== (+ 1 x) 1) x (/ (* x (log (+ 1 x))) (- (+ 1 x) 1)))) (log (+ 1 x))) (FPCore (i n) :name "Compound Interest" :alt (! :herbie-platform c (let ((lnbase (if (== (+ 1 (/ i n)) 1) (/ i n) (/ (* (/ i n) (log (+ 1 (/ i n)))) (- (+ (/ i n) 1) 1))))) (* 100 (/ (- (exp (* n lnbase)) 1) (/ i n))))) (* 100 (/ (- (pow (+ 1 (/ i n)) n) 1) (/ i n)))) (FPCore (x) :name "x / (x^2 + 1)" :alt (! :herbie-platform c (/ 1 (+ x (/ 1 x)))) (/ x (+ (* x x) 1))) (FPCore (a b c d) :name "Complex division, real part" :alt (! :herbie-platform c (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)) (+ (* c c) (* d d)))) (FPCore (a b c d) :name "Complex division, imag part" :alt (! :herbie-platform c (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)) (+ (* c c) (* d d)))) (FPCore (x) :name "arccos" (* 2 (atan (sqrt (/ (- 1 x) (+ 1 x)))))) ================================================ FILE: bench/numerics/fma.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (t) :name "fma_test1" :pre (<= 0.9 t 1.1) :alt (! :herbie-platform c (let ([x (+ 1 (* t 2e-16))] [z (- -1 (* 2 (* t 2e-16)))]) (fma x x z))) (let ([x (+ 1 (* t 2e-16))] [z (- -1 (* 2 (* t 2e-16)))]) (+ (* x x) z))) (FPCore (t) :name "fma_test2" :pre (<= 1.9 t 2.1) :alt (! :herbie-platform c (let ([x 1.7e308]) (fma x t (- x)))) (let ([x 1.7e308]) (- (* x t) x))) ================================================ FILE: bench/numerics/great-debate.fpcore ================================================ (FPCore (y) :name "Kahan's Monster" :pre (<= 1 y 9999) ; Integers only in Kahan's example but this is not essential (let ([Qx (- (fabs (- y (sqrt (+ (* y y) 1)))) (/ 1 (+ y (sqrt (+ (* y y) 1)))))]) (let ([z (* Qx Qx)]) (if (== z 0) 1 (/ (- (exp z) 1) z))))) (FPCore (y) :name "Kahan's Unum-Targeted Monster" :pre (<= 1 y 9999) ; Integers only in Kahan's example but this is not essential (let ([Qx (- (fabs (- y (sqrt (+ (* y y) 1)))) (/ 1 (+ y (sqrt (+ (* y y) 1)))))]) (let ([z (+ (* Qx Qx) (pow (pow 10 -300) (* 10000 (+ y 1))))]) (if (== z 0) 1 (/ (- (exp z) 1) z))))) (FPCore (x y) :name "Kahan p9 Example" :pre (and (< 0 x 1) (< y 1)) :alt (! :herbie-platform c (if (< 0.5 (fabs (/ x y)) 2) (/ (* (- x y) (+ x y)) (+ (* x x) (* y y))) (- 1 (/ 2 (+ 1 (* (/ x y) (/ x y))))))) (/ (* (- x y) (+ x y)) (+ (* x x) (* y y)))) (FPCore (t) :name "Kahan p13 Example 1" (let ([u (/ (* 2 t) (+ 1 t))]) (/ (+ 1 (* u u)) (+ 2 (* u u))))) (FPCore (t) :name "Kahan p13 Example 2" (let ([v (- 2 (/ (/ 2 t) (+ 1 (/ 1 t))))]) (/ (+ 1 (* v v)) (+ 2 (* v v))))) (FPCore (t) :name "Kahan p13 Example 3" (let ([v (- 2 (/ (/ 2 t) (+ 1 (/ 1 t))))]) (- 1 (/ 1 (+ 2 (* v v)))))) ================================================ FILE: bench/numerics/hamming-misc.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x eps) :name "NMSE Section 6.1 mentioned, A" (/ (- (* (+ 1 (/ 1 eps)) (exp (- (* (- 1 eps) x)))) (* (- (/ 1 eps) 1) (exp (- (* (+ 1 eps) x))))) 2)) (FPCore (a b) :name "NMSE Section 6.1 mentioned, B" (* (* (/ PI 2) (/ 1 (- (* b b) (* a a)))) (- (/ 1 a) (/ 1 b)))) (FPCore (x y) :name "Radioactive exchange between two surfaces" (- (pow x 4) (pow y 4))) ================================================ FILE: bench/numerics/kahan.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "Kahan's exp quotient" :alt (! :herbie-platform c (if (and (< x 1) (> x -1)) (/ (- (exp x) 1) (log (exp x))) (/ (- (exp x) 1) x))) (/ (- (exp x) 1) x)) ================================================ FILE: bench/numerics/libm.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x y z) :name "simple fma test" :alt (! :herbie-platform c -1) (- (fma x y z) (+ 1 (+ (* x y) z)))) (FPCore (x) :name "find-atanh" :alt (* 2 (atanh x)) (log (/ (+ 1 x) (- 1 x)))) ================================================ FILE: bench/numerics/martel.fpcore ================================================ ; -*- mode: scheme -*- (FPCore () :name "Rectangular parallelepiped of dimension a×b×c" :alt (! :herbie-platform c (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)]) (* d (+ (+ (* a b) (* b c)) (* c a))))) (FPCore (a b c d) :pre (and (<= 56789 a 98765) (<= 0 b 1) (<= 0 c 0.0016773) (<= 0 d 0.0016773)) :name "Expression, p14" :alt (! :herbie-platform c (+ (* 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" :alt (! :herbie-platform c (+ (+ d (+ c (+ a b))) e)) (+ (+ (+ (+ e d) c) b) a)) (FPCore (x) :pre (<= 0 x 2) :name "Expression 2, p15" :alt (! :herbie-platform c (* (+ 1.0 x) x)) (+ x (* x x))) (FPCore (x) :pre (<= 0 x 2) :name "Expression 3, p15" :alt (! :herbie-platform c (* (* (+ 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" :alt (! :herbie-platform c (+ (+ (+ (* 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" :alt (! :herbie-platform c (let ((e 2)) (+ (* (+ a b) e) (* (+ c d) e)))) (let ((e 2)) (* (+ a (+ b (+ c d))) e))) ================================================ FILE: bench/numerics/polynomial-cancellation.fpcore ================================================ ; -*- mode: scheme -*- (FPCore () :name "From Warwick Tucker's Validated Numerics" (let ([x 77617] [y 33096]) (+ (+ (+ (* 333.75 (pow y 6)) (* (* x x) (+ (+ (+ (* (* 11 (* x x)) (* y y)) (- (pow y 6))) (* -121 (pow y 4))) -2))) (* 5.5 (pow y 8))) (/ x (* 2 y))))) ================================================ FILE: bench/numerics/rosa.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (u v t1) :name "Rosa's DopplerBench" (/ (* (- t1) v) (* (+ t1 u) (+ t1 u)))) (FPCore (x) :name "Rosa's Benchmark" (- (* 0.954929658551372 x) (* 0.12900613773279798 (* (* x x) x)))) (FPCore (x1 x2) :name "Rosa's FloatVsDoubleBenchmark" (let ((t (- (+ (* (* 3 x1) x1) (* 2 x2)) x1)) (t* (- (- (* (* 3 x1) x1) (* 2 x2)) x1)) (d (+ (* x1 x1) 1))) (let ((s (/ t d)) (s* (/ t* d))) (+ x1 (+ (* (+ (* (* 2 x1) s (- s 3)) (* (* x1 x1) (- (* 4 s) 6))) d) (* 3 x1 x1 s) (* x1 x1 x1) x1 (* 3 s*)))))) (FPCore (v w r) :name "Rosa's TurbineBenchmark" (- (+ 3 (/ 2 (* r r))) (/ (* 0.125 (- 3 (* 2 v)) (* w w r r)) (- 1 v)) 4.5)) ================================================ FILE: bench/numerics/rump.fpcore ================================================ (FPCore (x y) :name "Rump's expression from Stadtherr's award speech" :pre (and (== x 77617) (== y 33096)) :spec -54767/66192 (+ (+ (+ (* 333.75 (pow y 6)) (* (* x x) (- (- (- (* (* (* (* 11 x) x) y) y) (pow y 6)) (* 121 (pow y 4))) 2))) (* 5.5 (pow y 8))) (/ x (* 2 y)))) ;; From ;; How Reliable are the Results of Computers ;; Jahrbuch Uberblicke Mathematik (1983) (FPCore (x y) :name "From Rump in a 1983 paper" :pre (and (== x 10864) (== y 18817)) ;:pre (and (< 10500 x 11000) (< 18500 y 19000)) (+ (- (* 9 (pow x 4)) (pow y 4)) (* 2 (* y y)))) (FPCore (x y) :name "From Rump in a 1983 paper, rewritten" :pre (and (== x 10864) (== y 18817)) ;:pre (and (< 10500 x 11000) (< 18500 y 19000)) (- (* 9 (pow x 4)) (* (* y y) (- (* y y) 2)))) ================================================ FILE: bench/physics/ballistics.fpcore ================================================ (FPCore (v H) :name "Optimal throwing angle" (let ([g 9.8]) (atan (/ v (sqrt (- (* v v) (* (* 2 g) H))))))) ================================================ FILE: bench/physics/dimer-escape.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (J K U) :name "Maksimov and Kolovsky, Equation (3)" (* (* (* -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)" (+ (* (* J (- (exp l) (exp (- l)))) (cos (/ K 2))) U)) (FPCore (K m n M l) :name "Maksimov and Kolovsky, Equation (32)" (* (cos (- (/ (* K (+ m n)) 2) M)) (exp (- (- (pow (- (/ (+ m n) 2) M) 2)) (- l (fabs (- m n))))))) ================================================ FILE: bench/physics/gated-magnetic-field.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (NdChar Ec Vef EDonor mu KbT NaChar Ev EAccept) :name "Bulmash initializePoisson" (+ (/ NdChar (+ 1 (exp (/ (- (- (- (- Ec Vef) EDonor) mu)) KbT)))) (/ NaChar (+ 1 (exp (/ (+ (+ (+ Ev Vef) EAccept) (- mu)) KbT)))))) ================================================ FILE: bench/physics/gravitation.fpcore ================================================ ; -*- mode: scheme -*- ;; Translated from: ;; https://github.com/sandialabs/elaenia/blob/main/examples/03_vectors/gravitation.c ;; Original comments cite: ;; - Ellipsoidal gravity vector source: ;; https://www.mathworks.com/matlabcentral/fileexchange/8359-ellipsoidal-gravity-vector/files/xyz2grav.m ;; - Example ECEF coordinates source: ;; https://dominoc925-pages.appspot.com/mapplets/cs_ecef.html ;; - Constants/provenance in the C example: ;; WGS84 Earth gravitational constant and equatorial radius, with J2-only ;; ellipsoidal gravity correction ("gravity anomaly" terms omitted). (FPCore norm-3 ((a 3)) :name "3D vector norm" :precision binary64 :pre (and (<= (fabs (ref a 0)) 1e154) (<= (fabs (ref a 1)) 1e154) (<= (fabs (ref a 2)) 1e154)) (sqrt (+ (* (ref a 0) (ref a 0)) (+ (* (ref a 1) (ref a 1)) (* (ref a 2) (ref a 2)))))) (FPCore gravitation-ecef ((r_e 3)) :name "ECEF gravitation vector" :precision binary64 :pre (and (<= (fabs (ref r_e 0)) 1e154) (<= (fabs (ref r_e 1)) 1e154) (<= (fabs (ref r_e 2)) 1e154) (< 0 (sqrt (+ (* (ref r_e 0) (ref r_e 0)) (+ (* (ref r_e 1) (ref r_e 1)) (* (ref r_e 2) (ref r_e 2))))))) (let* ([MU 3.986004418e14] [R 6378137.0] [J2 0.00108263] [r (sqrt (+ (* (ref r_e 0) (ref r_e 0)) (+ (* (ref r_e 1) (ref r_e 1)) (* (ref r_e 2) (ref r_e 2)))))] [sub1 (* (* 1.5 J2) (/ (* R R) (* r r)))] [sub2 (/ (* (* 5 (ref r_e 2)) (ref r_e 2)) (* r r))] [sub3 (/ (- MU) (* (* r r) r))] [sub4 (* sub3 (- 1 (* sub1 (- sub2 1.0))))]) (array (* (ref r_e 0) sub4) (* (ref r_e 1) sub4) (* (* (ref r_e 2) sub3) (- 1 (* sub1 (- sub2 3.0))))))) ================================================ FILE: bench/physics/kalman.fpcore ================================================ (FPCore (dt r) :name "Kalman filter per K" :pre (and (> dt 0) (> r 0)) ; initializing matrices (let ([p00 25.0] [p01 0.0] [p02 0.0] [p10 0.0] [p11 10.0] [p12 0.0] [p20 0.0] [p21 0.0] [p22 1.0] [f00 1.0] [f01 dt] [f02 (* 0.5 (* dt dt))] [f10 0.0] [f11 1.0] [f12 dt] [f20 0.0] [f21 0.0] [f22 1.0] [q00 (* 0.25 (* dt (* dt (* dt dt))))] [q01 (* 0.5 (* dt (* dt dt)))] [q02 (* 0.5 (* dt dt))] [q10 (* 0.5 (* dt (* dt dt)))] [q11 (* dt dt)] [q12 dt] [q20 (* 0.5 (* dt dt))] [q21 dt] [q22 1]) ; axbT_33(P, F, P) (let ([p00* (+ (+ (* p00 f00) (* p01 f01)) (* p02 f02))] [p01* (+ (+ (* p00 f10) (* p01 f11)) (* p02 f12))] [p02* (+ (+ (* p00 f20) (* p01 f21)) (* p02 f22))] [p10* (+ (+ (* p10 f00) (* p11 f01)) (* p12 f02))] [p11* (+ (+ (* p10 f10) (* p11 f11)) (* p12 f12))] [p12* (+ (+ (* p10 f20) (* p11 f21)) (* p12 f22))] [p20* (+ (+ (* p20 f00) (* p21 f01)) (* p22 f02))] [p21* (+ (+ (* p20 f10) (* p21 f11)) (* p22 f12))] [p22* (+ (+ (* p20 f20) (* p21 f21)) (* p22 f22))]) ; axb_33(F, P, P) (let ([p00** (+ (+ (* f00 p00*) (* f01 p10*)) (* f02 p20*))] [p01** (+ (+ (* f00 p01*) (* f01 p11*)) (* f02 p21*))] [p02** (+ (+ (* f00 p02*) (* f01 p12*)) (* f02 p22*))] [p10** (+ (+ (* f10 p00*) (* f11 p10*)) (* f12 p20*))] [p11** (+ (+ (* f10 p01*) (* f11 p11*)) (* f12 p21*))] [p12** (+ (+ (* f10 p02*) (* f11 p12*)) (* f12 p22*))] [p20** (+ (+ (* f20 p00*) (* f21 p10*)) (* f22 p20*))] [p21** (+ (+ (* f20 p01*) (* f21 p11*)) (* f22 p21*))] [p22** (+ (+ (* f20 p02*) (* f21 p12*)) (* f22 p22*))]) ; a_add_b_33(P, Q, P) (let ([p00*** (+ p00** q00)] [p01*** (+ p01** q01)] [p02*** (+ p02** q02)] [p10*** (+ p10** q10)] [p11*** (+ p11** q11)] [p12*** (+ p12** q12)] [p20*** (+ p20** q20)] [p21*** (+ p21** q21)] [p22*** (+ p22** q22)]) ; update_K (let ([K0 (/ p00*** (+ p00*** r))] [K1 (/ p10*** (+ p00*** r))] [K2 (/ p20*** (+ p00*** r))]) K0)))))) (FPCore (x0 x1 x2 dt r sensor) :name "Kalman filter per x" :pre (and (> dt 0) (> r 0) (> sensor 0)) ; initializing matrices (let ([p00 25.0] [p01 0.0] [p02 0.0] [p10 0.0] [p11 10.0] [p12 0.0] [p20 0.0] [p21 0.0] [p22 1.0] [f00 1.0] [f01 dt] [f02 (* 0.5 (* dt dt))] [f10 0.0] [f11 1.0] [f12 dt] [f20 0.0] [f21 0.0] [f22 1.0] [q00 (* 0.25 (* dt (* dt (* dt dt))))] [q01 (* 0.5 (* dt (* dt dt)))] [q02 (* 0.5 (* dt dt))] [q10 (* 0.5 (* dt (* dt dt)))] [q11 (* dt dt)] [q12 dt] [q20 (* 0.5 (* dt dt))] [q21 dt] [q22 1]) ; axbT_33(P, F, P) (let ([p00* (+ (+ (* p00 f00) (* p01 f01)) (* p02 f02))] [p01* (+ (+ (* p00 f10) (* p01 f11)) (* p02 f12))] [p02* (+ (+ (* p00 f20) (* p01 f21)) (* p02 f22))] [p10* (+ (+ (* p10 f00) (* p11 f01)) (* p12 f02))] [p11* (+ (+ (* p10 f10) (* p11 f11)) (* p12 f12))] [p12* (+ (+ (* p10 f20) (* p11 f21)) (* p12 f22))] [p20* (+ (+ (* p20 f00) (* p21 f01)) (* p22 f02))] [p21* (+ (+ (* p20 f10) (* p21 f11)) (* p22 f12))] [p22* (+ (+ (* p20 f20) (* p21 f21)) (* p22 f22))]) ; axb_33(F, P, P) (let ([p00** (+ (+ (* f00 p00*) (* f01 p10*)) (* f02 p20*))] [p01** (+ (+ (* f00 p01*) (* f01 p11*)) (* f02 p21*))] [p02** (+ (+ (* f00 p02*) (* f01 p12*)) (* f02 p22*))] [p10** (+ (+ (* f10 p00*) (* f11 p10*)) (* f12 p20*))] [p11** (+ (+ (* f10 p01*) (* f11 p11*)) (* f12 p21*))] [p12** (+ (+ (* f10 p02*) (* f11 p12*)) (* f12 p22*))] [p20** (+ (+ (* f20 p00*) (* f21 p10*)) (* f22 p20*))] [p21** (+ (+ (* f20 p01*) (* f21 p11*)) (* f22 p21*))] [p22** (+ (+ (* f20 p02*) (* f21 p12*)) (* f22 p22*))]) ; a_add_b_33(P, Q, P) (let ([p00*** (+ p00** q00)] [p01*** (+ p01** q01)] [p02*** (+ p02** q02)] [p10*** (+ p10** q10)] [p11*** (+ p11** q11)] [p12*** (+ p12** q12)] [p20*** (+ p20** q20)] [p21*** (+ p21** q21)] [p22*** (+ p22** q22)]) ; update_K (let ([K0 (/ p00*** (+ p00*** r))] [K1 (/ p10*** (+ p00*** r))] [K2 (/ p20*** (+ p00*** r))]) ; predict_x (let ([x0* (+ x0 (+ x1 (* 0.5 (* dt (* dt x2)))))] [x1* (+ x1 x2)] [x2* x2]) (let ([y (- sensor x0*)]) ; update_x (let ([x0** (+ x0* (* K0 y))] [x1** (+ x1* (* K1 y))] [x2** (+ x2* (* K2 y))]) x0**))))))))) (FPCore (dt r) :name "Kalman filter per P" :pre (and (> dt 0) (> r 0)) ; initializing matrices (let ([p00 25.0] [p01 0.0] [p02 0.0] [p10 0.0] [p11 10.0] [p12 0.0] [p20 0.0] [p21 0.0] [p22 1.0] [f00 1.0] [f01 dt] [f02 (* 0.5 (* dt dt))] [f10 0.0] [f11 1.0] [f12 dt] [f20 0.0] [f21 0.0] [f22 1.0] [q00 (* 0.25 (* dt (* dt (* dt dt))))] [q01 (* 0.5 (* dt (* dt dt)))] [q02 (* 0.5 (* dt dt))] [q10 (* 0.5 (* dt (* dt dt)))] [q11 (* dt dt)] [q12 dt] [q20 (* 0.5 (* dt dt))] [q21 dt] [q22 1]) ; axbT_33(P, F, P) (let ([p00* (+ (+ (* p00 f00) (* p01 f01)) (* p02 f02))] [p01* (+ (+ (* p00 f10) (* p01 f11)) (* p02 f12))] [p02* (+ (+ (* p00 f20) (* p01 f21)) (* p02 f22))] [p10* (+ (+ (* p10 f00) (* p11 f01)) (* p12 f02))] [p11* (+ (+ (* p10 f10) (* p11 f11)) (* p12 f12))] [p12* (+ (+ (* p10 f20) (* p11 f21)) (* p12 f22))] [p20* (+ (+ (* p20 f00) (* p21 f01)) (* p22 f02))] [p21* (+ (+ (* p20 f10) (* p21 f11)) (* p22 f12))] [p22* (+ (+ (* p20 f20) (* p21 f21)) (* p22 f22))]) ; axb_33(F, P, P) (let ([p00** (+ (+ (* f00 p00*) (* f01 p10*)) (* f02 p20*))] [p01** (+ (+ (* f00 p01*) (* f01 p11*)) (* f02 p21*))] [p02** (+ (+ (* f00 p02*) (* f01 p12*)) (* f02 p22*))] [p10** (+ (+ (* f10 p00*) (* f11 p10*)) (* f12 p20*))] [p11** (+ (+ (* f10 p01*) (* f11 p11*)) (* f12 p21*))] [p12** (+ (+ (* f10 p02*) (* f11 p12*)) (* f12 p22*))] [p20** (+ (+ (* f20 p00*) (* f21 p10*)) (* f22 p20*))] [p21** (+ (+ (* f20 p01*) (* f21 p11*)) (* f22 p21*))] [p22** (+ (+ (* f20 p02*) (* f21 p12*)) (* f22 p22*))]) ; a_add_b_33(P, Q, P) (let ([p00*** (+ p00** q00)] [p01*** (+ p01** q01)] [p02*** (+ p02** q02)] [p10*** (+ p10** q10)] [p11*** (+ p11** q11)] [p12*** (+ p12** q12)] [p20*** (+ p20** q20)] [p21*** (+ p21** q21)] [p22*** (+ p22** q22)]) ; update_K (let ([K0 (/ p00*** (+ p00*** r))] [K1 (/ p10*** (+ p00*** r))] [K2 (/ p20*** (+ p00*** r))]) ; update_P (let ([eyekh00 (- 1 K0)] [eyekh01 0.0] [eyekh02 0.0] [eyekh10 (- K1)] [eyekh11 1.0] [eyekh12 0.0] [eyekh20 (- K2)] [eyekh21 0.0] [eyekh22 1.0]) ; axb_33(&eyekh, P, P) (let ([p00**** (+ (+ (* eyekh00 p00***) (* eyekh01 p10***)) (* eyekh02 p20***))] [p01**** (+ (+ (* eyekh00 p01***) (* eyekh01 p11***)) (* eyekh02 p21***))] [p02**** (+ (+ (* eyekh00 p02***) (* eyekh01 p12***)) (* eyekh02 p22***))] [p10**** (+ (+ (* eyekh10 p00***) (* eyekh11 p10***)) (* eyekh12 p20***))] [p11**** (+ (+ (* eyekh10 p01***) (* eyekh11 p11***)) (* eyekh12 p21***))] [p12**** (+ (+ (* eyekh10 p02***) (* eyekh11 p12***)) (* eyekh12 p22***))] [p20**** (+ (+ (* eyekh20 p00***) (* eyekh21 p10***)) (* eyekh22 p20***))] [p21**** (+ (+ (* eyekh20 p01***) (* eyekh21 p11***)) (* eyekh22 p21***))] [p22**** (+ (+ (* eyekh20 p02***) (* eyekh21 p12***)) (* eyekh22 p22***))]) p00****)))))))) ================================================ FILE: bench/physics/multiphoton-states.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (k n) :name "Migdal et al, Equation (51)" (* (/ 1 (sqrt k)) (pow (* (* 2 PI) n) (/ (- 1 k) 2)))) (FPCore (a1 a2 th) :name "Migdal et al, Equation (64)" (+ (* (/ (cos th) (sqrt 2)) (* a1 a1)) (* (/ (cos th) (sqrt 2)) (* a2 a2)))) ================================================ FILE: bench/physics/quantum-walk.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (v t) :name "Falkner and Boettcher, Equation (20:1,3)" (/ (- 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 (* v v))) (sqrt (- 2 (* 6 (* v v))))))) (FPCore (a k m) :name "Falkner and Boettcher, Appendix A" (/ (* a (pow k m)) (+ (+ 1 (* 10 k)) (* k k)))) (FPCore (v) :name "Falkner and Boettcher, Appendix B, 1" (acos (/ (- 1 (* 5 (* v v))) (- (* v v) 1)))) (FPCore (v) :name "Falkner and Boettcher, Appendix B, 2" (* (* (/ (sqrt 2) 4) (sqrt (- 1 (* 3 (* v v))))) (- 1 (* v v)))) ================================================ FILE: bench/physics/sidey.fpcore ================================================ ;; Code courtesy of Sidey P. Timmins of NASA ;; In the original, x was (- q r) (FPCore (p x) :name "Given's Rotation SVD example" :pre (< 1e-150 (fabs x) 1e150) :alt (! :herbie-platform c (sqrt (+ 1/2 (/ (copysign 1/2 x) (hypot 1 (/ (* 2 p) x)))))) (sqrt (* 0.5 (+ 1 (/ x (sqrt (+ (* (* 4 p) p) (* x x)))))))) ;; Here, I'm doing (1 - the above), and x here is (2p / x) (FPCore (x) :name "Given's Rotation SVD example, simplified" (- 1 (sqrt (* 1/2 (+ 1 (/ 1 (hypot 1 x))))))) ================================================ FILE: bench/physics/superfluidity-breakdown.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (t l Om Omc) :name "Toniolo and Linder, Equation (2)" (asin (sqrt (/ (- 1 (pow (/ Om Omc) 2)) (+ 1 (* 2 (pow (/ t l) 2))))))) (FPCore (l Om kx ky) :name "Toniolo and Linder, Equation (3a)" (sqrt (* (/ 1 2) (+ 1 (/ 1 (sqrt (+ 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 (+ (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)) (+ (* l l) (* 2 (* t t)))) (* l l))))) (FPCore (t l k) :name "Toniolo and Linder, Equation (10+)" (/ 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) (* 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 (/ (* l l) Om))) (* (* n (pow (/ l Om) 2)) (- U U*)))))) ================================================ FILE: bench/physics/tea-flows.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (F l) :name "VandenBroeck and Keller, Equation (6)" (- (* PI l) (* (/ 1 (* F F)) (tan (* PI l))))) (FPCore (f) :name "VandenBroeck and Keller, Equation (20)" (let ((PI/4 (/ PI 4))) (let ((exp+ (exp (* PI/4 f)))) (let ((exp- (exp (- (* PI/4 f))))) (- (* (/ 1 PI/4) (log (/ (+ exp+ exp-) (- exp+ exp-))))))))) (FPCore (F B x) :name "VandenBroeck and Keller, Equation (23)" (+ (- (* x (/ 1 (tan B)))) (* (/ F (sin B)) (pow (+ (+ (* F F) 2) (* 2 x)) (- (/ 1 2)))))) (FPCore (B x) :name "VandenBroeck and Keller, Equation (24)" (+ (- (* x (/ 1 (tan B)))) (/ 1 (sin B)))) ================================================ FILE: bench/physics/tea-whistle.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (c0 A V l) :name "Henrywood and Agarwal, Equation (3)" (* c0 (sqrt (/ A (* V l))))) (FPCore (w0 M D h l d) :name "Henrywood and Agarwal, Equation (9a)" (* 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) (pow (/ (* M D) (* 2 d)) 2)) (/ h l))))) (FPCore (c0 w h D d M) :name "Henrywood and Agarwal, Equation (13)" (let ((x (/ (* c0 (* d d)) (* (* w h) (* D D))))) (* (/ c0 (* 2 w)) (+ x (sqrt (- (* x x) (* M M))))))) ================================================ FILE: bench/physics/universal-linear-optics.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (a b) :name "Bouland and Aaronson, Equation (24)" (- (+ (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)" (- (+ (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)" (- (+ (pow (+ (* a a) (* b b)) 2) (* 4 (* b b))) 1)) ================================================ FILE: bench/proj/krovak.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (phi0 es) :name "setup-alpha" :pre (and (>= es 0.0) (< es 1.0)) (sqrt (+ 1.0 (/ (* es (pow (cos phi0) 4.0)) (- 1.0 es))))) (FPCore (u0 phi0 alpha g) :name "setup-k" :pre (and (> g 0.0) (> alpha 0.0)) (let* ([tan-u0 (tan (+ (* 0.5 u0) (/ PI 4.0)))] [tan-phi0 (tan (+ (* 0.5 phi0) (/ PI 4.0)))] [pow-term (pow tan-phi0 alpha)]) (/ (* tan-u0 g) pow-term))) (FPCore (phi0 es) :name "setup-n0" :pre (and (>= es 0.0) (< es 1.0)) (/ (sqrt (- 1.0 es)) (- 1.0 (* es (* (sin phi0) (sin phi0)))))) (FPCore (k0 n0) :name "setup-rho0" :pre (and (> k0 0.0) (> n0 0.0)) (let* ([S0 1.37008346281555] [tanS0 (tan S0)]) (/ (* k0 n0) tanS0))) (FPCore (phi evar alpha) :name "forward-gfi" :pre (and (>= evar 0.0) (< evar 1.0)) (pow (/ (+ 1.0 (* evar (sin phi))) (- 1.0 (* evar (sin phi)))) (* 0.5 (* alpha evar)))) (FPCore (phi k alpha gfi) :name "forward-u" :pre (and (> k 0.0) (> alpha 0.0) (> gfi 0.0)) (let* ([tan-term (tan (+ (* 0.5 phi) (/ PI 4.0)))] [pow-term (pow tan-term alpha)] [ratio (/ (* k pow-term) gfi)]) (* 2.0 (- (atan ratio) (/ PI 4.0))))) (FPCore (u ad deltav) :name "krovak-forward-s" (asin (+ (* (cos ad) (sin u)) (* (sin ad) (* (cos u) (cos deltav)))))) (FPCore (rho0 s n) :name "forward-rho" :pre (and (> rho0 0.0) (> n 0.0)) (let* ([S0 1.37008346281555] [tan-s0 (tan (+ (* 0.5 S0) (/ PI 4.0)))] [tan-term (tan (+ (* 0.5 s) (/ PI 4.0)))] [num (pow tan-s0 n)] [den (pow tan-term n)]) (/ (* rho0 num) den))) (FPCore (Xr Yr) :name "mod-dX" (let* ([C1 2.946529277E-02] [C3 1.193845912E-07] [C4 -4.668270147E-07] [C5 9.233980362E-12] [C6 1.523735715E-12] [C7 1.696780024E-18] [C8 4.408314235E-18] [C9 -8.331083518E-24] [C10 -3.689471323E-24] [Xr2 (* Xr Xr)] [Yr2 (* Yr Yr)] [Xr4 (* Xr2 Xr2)] [Yr4 (* Yr2 Yr2)] [diff (- Xr2 Yr2)]) (+ C1 (+ (* C3 Xr) (+ (* (- C4) Yr) (+ (* -2.0 (* C6 (* Xr Yr))) (+ (* C5 diff) (+ (* C7 (* Xr (- Xr2 (* 3.0 Yr2)))) (+ (* (- C8) (* Yr (- (* 3.0 Xr2) Yr2))) (+ (* 4.0 (* C9 (* Xr (* Yr diff)))) (* C10 (+ Xr4 (+ Yr4 (* -6.0 (* Xr2 Yr2))))))))))))))) (FPCore (Xr Yr) :name "mod-dY" (let* ([C2 2.515965696E-02] [C3 1.193845912E-07] [C4 -4.668270147E-07] [C5 9.233980362E-12] [C6 1.523735715E-12] [C7 1.696780024E-18] [C8 4.408314235E-18] [C9 -8.331083518E-24] [C10 -3.689471323E-24] [Xr2 (* Xr Xr)] [Yr2 (* Yr Yr)] [Xr4 (* Xr2 Xr2)] [Yr4 (* Yr2 Yr2)] [diff (- Xr2 Yr2)]) (+ C2 (+ (* C3 Yr) (+ (* C4 Xr) (+ (* 2.0 (* C5 (* Xr Yr))) (+ (* C6 diff) (+ (* C8 (* Xr (- Xr2 (* 3.0 Yr2)))) (+ (* C7 (* Yr (- (* 3.0 Xr2) Yr2))) (+ (* -4.0 (* C10 (* Xr (* Yr diff)))) (* C9 (+ Xr4 (+ Yr4 (* -6.0 (* Xr2 Yr2))))))))))))))) (FPCore (rho0 rho n) :name "inverse-s" :pre (and (> rho0 0.0) (> rho 0.0)) (let* ([S0 1.37008346281555] [tan-s0 (tan (+ (* 0.5 S0) (/ PI 4.0)))] [ratio (/ rho0 rho)] [pow-term (pow ratio (/ 1.0 n))] [product (* pow-term tan-s0)]) (* 2.0 (- (atan product) (/ PI 4.0))))) (FPCore (ad s d) :name "inverse-u" (asin (+ (* (cos ad) (sin s)) (* -1.0 (* (sin ad) (* (cos s) (cos d))))))) (FPCore (u k alpha evar fi) :name "inverse-phi-iter" :pre (and (> k 0.0) (> alpha 0.0) (>= evar 0.0) (< evar 1.0)) (let* ([pow-k (pow k (/ -1.0 alpha))] [tan-term (tan (+ (* 0.5 u) (/ PI 4.0)))] [pow-tan (pow tan-term (/ 1.0 alpha))] [ratio (/ (+ 1.0 (* evar (sin fi))) (- 1.0 (* evar (sin fi))))] [pow-ratio (pow ratio (* 0.5 evar))] [product (* pow-k (* pow-tan pow-ratio))]) (* 2.0 (- (atan product) (/ PI 4.0))))) ================================================ FILE: bench/proj/omerc.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (E_var tsfn_phi B) :name "forward-W" (/ E_var (pow tsfn_phi B))) (FPCore (W) :name "forward-half-diff" (* 0.5 (- W (/ 1.0 W)))) (FPCore (W) :name "forward-half-sum" (* 0.5 (+ W (/ 1.0 W)))) (FPCore (S T singam cosgam B lam) :name "forward-U" (let ([V (sin (* B lam))]) (/ (- (* S singam) (* V cosgam)) T))) (FPCore (U ArB) :name "forward-v" (* 0.5 (* ArB (log (/ (- 1.0 U) (+ 1.0 U)))))) (FPCore (S B lam cosgam singam ArB) :name "forward-u-atan" (let* ([V (sin (* B lam))] [temp (cos (* B lam))] [numer (+ (* S cosgam) (* V singam))]) (* ArB (atan2 numer temp)))) (FPCore (u v cosrot sinrot u0) :name "forward-rot-x" (let ([udiff (- u u0)]) (+ (* v cosrot) (* udiff sinrot)))) (FPCore (u v cosrot sinrot u0) :name "forward-rot-y" (let ([udiff (- u u0)]) (- (* udiff cosrot) (* v sinrot)))) (FPCore (BrA v) :name "inverse-Qp" (exp (- (* BrA v)))) (FPCore (Sp Tp Vp singam cosgam) :name "inverse-Up" (/ (+ (* Vp cosgam) (* Sp singam)) Tp)) (FPCore (Up Esc) :name "inverse-prephi" (/ Esc (sqrt (/ (+ 1.0 Up) (- 1.0 Up))))) (FPCore (Sp Vp cosgam singam BrA u rB) :name "inverse-lambda" (let ([temp (cos (* BrA u))] [numer (- (* Sp cosgam) (* Vp singam))]) (- (* rB (atan2 numer temp))))) (FPCore (phi0 es) :name "init-con" :pre (and (>= es 0.0) (< es 1.0)) (let ([sinphi0 (sin phi0)]) (- 1.0 (* es (* sinphi0 sinphi0))))) (FPCore (phi0 es one_es) :name "init-B" :pre (and (>= es 0.0) (< es 1.0) (> one_es 0.0)) (let* ([cosphi0 (cos phi0)] [cos2 (* cosphi0 cosphi0)] [cos4 (* cos2 cos2)]) (sqrt (+ 1.0 (/ (* es cos4) one_es))))) (FPCore (phi0 es one_es com) :name "init-D" :pre (and (>= es 0.0) (< es 1.0) (> one_es 0.0) (> com 0.0)) (let* ([sinphi0 (sin phi0)] [cosphi0 (cos phi0)] [cos2 (* cosphi0 cosphi0)] [cos4 (* cos2 cos2)] [con (- 1.0 (* es (* sinphi0 sinphi0)))] [B (sqrt (+ 1.0 (/ (* es cos4) one_es)))] [root (sqrt con)]) (/ (* B com) (* cosphi0 root)))) (FPCore (D phi0) :name "init-F" (let ([Fraw (- (* D D) 1.0)]) (if (<= Fraw 0.0) 0.0 (let ([Froot (sqrt Fraw)]) (if (< phi0 0.0) (- Froot) Froot))))) (FPCore (alpha_c D) :name "init-gamma0-from-alpha" (asin (/ (sin alpha_c) D))) (FPCore (gamma0 D) :name "init-alpha-from-gamma0" (asin (* D (sin gamma0)))) (FPCore (lamc F gamma0 B) :name "init-lam0-alpha" (let* ([half_diff (* 0.5 (- F (/ 1.0 F)))] [angle (asin (* half_diff (tan gamma0)))]) (- lamc (/ angle B)))) (FPCore (L H) :name "init-p" (/ (- L H) (+ L H))) (FPCore (E_var L H) :name "init-J" (let ([E2 (* E_var E_var)] [LH (* L H)]) (/ (- E2 LH) (+ E2 LH)))) (FPCore (lam1 lam2 B J p) :name "init-lam0-twopoint" (let* ([delta (- lam1 lam2)] [lam2_adj (if (< delta (- PI)) (+ lam2 (* 2.0 PI)) (if (> delta PI) (- lam2 (* 2.0 PI)) lam2))] [delta_adj (- lam1 lam2_adj)] [center (* 0.5 (+ lam1 lam2_adj))] [halfBdelta (* 0.5 (* B delta_adj))] [angle (atan (/ (* J (tan halfBdelta)) p))]) (- center (/ angle B)))) (FPCore (ArB D alpha_c phi0) :name "init-u0" (let* ([ratio (/ (sqrt (- (* D D) 1.0)) (cos alpha_c))] [angle (atan ratio)] [scaled (* ArB angle)] [mag (fabs scaled)]) (if (< phi0 0.0) (- mag) mag))) (FPCore (ArB gamma0) :name "init-v-pole-n" (* ArB (log (tan (- (/ PI 4.0) (* 0.5 gamma0)))))) (FPCore (phi e) :name "pj_tsfn" (let* ([sinphi (sin phi)] [e_sin (* e sinphi)]) (* (tan (* 0.5 (- (/ PI 2) phi))) (pow (/ (+ 1.0 e_sin) (- 1.0 e_sin)) (* 0.5 e))))) ================================================ FILE: bench/proj/som.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (lam p22 sa t w q) :name "seraz0-s" (let* ([sd (sin lam)] [sdsq (* sd sd)] [numer (+ 1.0 (* t sdsq))] [denom (* (+ 1.0 (* w sdsq)) (+ 1.0 (* q sdsq)))]) (* p22 (* sa (* (cos lam) (sqrt (/ numer denom))))))) (FPCore (lam p22 ca q w) :name "seraz0-h" (let* ([sd (sin lam)] [sdsq (* sd sd)] [d1 (+ 1.0 (* q sdsq))] [wterm (+ 1.0 (* w sdsq))] [ratio (/ (+ 1.0 (* q sdsq)) wterm)] [scale (- (/ wterm (* d1 d1)) (* p22 ca))]) (* (sqrt ratio) scale))) (FPCore (mult h xj s) :name "seraz0-fc-a2" (let* ([sq (sqrt (+ (* xj xj) (* s s)))]) (* mult (/ (- (* h xj) (* s s)) sq)))) (FPCore (mult h xj s) :name "seraz0-fc-c1" (let* ([sq (sqrt (+ (* xj xj) (* s s)))]) (* mult (/ (* s (+ h xj)) sq)))) (FPCore (lamt one_es tanphi sa ca) :name "forward-xlam" (/ (+ (* one_es (* tanphi sa)) (* (sin lamt) ca)) (cos lamt))) (FPCore (phi es one_es ca sa lamt) :name "forward-phidp" (let* ([sp (sin phi)] [cosphi (cos phi)] [sinlamt (sin lamt)] [denom (sqrt (- 1.0 (* es (* sp sp))))] [arg (/ (- (* one_es (* ca sp)) (* sa (* cosphi sinlamt))) denom)]) (asin arg))) (FPCore (phidp) :name "forward-tanph" (let ([theta (+ (/ PI 4.0) (* 0.5 phidp))]) (log (tan theta)))) (FPCore (lamdp b a2 a4 tanph s xj) :name "forward-xy-x" (let* ([d (sqrt (+ (* xj xj) (* s s)))]) (+ (* b lamdp) (+ (* a2 (sin (* 2.0 lamdp))) (+ (* a4 (sin (* 4.0 lamdp))) (- (* tanph (/ s d)))))))) (FPCore (lamdp c1 c3 tanph s xj) :name "forward-xy-y" (let* ([d (sqrt (+ (* xj xj) (* s s)))] [sd (sin lamdp)]) (+ (* c1 sd) (+ (* c3 (sin (* 3.0 lamdp))) (* tanph (/ xj d)))))) (FPCore (lamdp xy_x xy_y s xj a2 a4 c1 c3 b) :name "inverse-lamdp-update" (let* ([sinlam (sin lamdp)] [sin2 (sin (* 2.0 lamdp))] [sin3 (sin (* 3.0 lamdp))] [sin4 (sin (* 4.0 lamdp))] [ratio (/ s xj)] [base (+ xy_x (* xy_y ratio))] [poly (- (- base (* a2 sin2)) (* a4 sin4))] [corr (* ratio (+ (* c1 sinlam) (* c3 sin3)))]) (/ (- poly corr) b))) (FPCore (s xj xy_y c1 c3 lamdp) :name "inverse-fac" (let* ([ratio (/ (* s s) (* xj xj))] [scale (sqrt (+ 1.0 ratio))] [sl (sin lamdp)] [inner (- xy_y (+ (* c1 sl) (* c3 (sin (* 3.0 lamdp)))))]) (exp (* scale inner)))) (FPCore (fac) :name "inverse-phidp" (* 2.0 (- (atan fac) (/ PI 4.0)))) (FPCore (lamdp spp q u rone_es ca sa) :name "inverse-lamt" (let* ([sppsq (* spp spp)] [dd (* (sin lamdp) (sin lamdp))] [coslam (cos lamdp)] [denom (- 1.0 (* sppsq (+ 1.0 u)))] [kterm (+ 1.0 (* q dd))] [root (sqrt (- (* kterm (- 1.0 sppsq)) (* sppsq u)))] [term1 (* (- 1.0 (* sppsq rone_es)) (* (tan lamdp) ca))] [term2 (/ (* spp (* sa root)) coslam)] [frac (/ (- term1 term2) denom)]) (atan frac))) (FPCore (lamdp lamt ca one_es sa) :name "inverse-phi" (atan (/ (- (* (tan lamdp) (cos lamt)) (* ca (sin lamt))) (* one_es sa)))) (FPCore (spp es one_es) :name "inverse-phi-degenerate" (let* ([one_es2 (* one_es one_es)] [denom (sqrt (+ one_es2 (* es (* spp spp))))]) (asin (/ spp denom)))) (FPCore (es ca rone_es) :name "setup-w" :pre (> rone_es 0.0) (let* ([esc (* es (* ca ca))] [scaled (* (- 1.0 esc) rone_es)]) (- (* scaled scaled) 1.0))) (FPCore (es sa rone_es) :name "setup-t" :pre (> rone_es 0.0) (let ([ess (* es (* sa sa))] [rone2 (* rone_es rone_es)]) (* ess (* (- 2.0 es) rone2)))) (FPCore (one_es) :name "setup-xj" :pre (> one_es 0.0) (* one_es (* one_es one_es))) ================================================ FILE: bench/proj/somerc.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (phi) :name "forward-tanlog" (log (tan (+ (/ PI 4.0) (* 0.5 phi))))) (FPCore (phi ecc) :name "forward-ellipsoid-logratio" :pre (and (> ecc 0.0) (< ecc 1.0)) (let ([sp (* ecc (sin phi))]) (log (/ (+ 1.0 sp) (- 1.0 sp))))) (FPCore (phi ecc c hlf_e K) :name "forward-phip" :pre (and (> ecc 0.0) (< ecc 1.0)) (let* ([sp (* ecc (sin phi))] [log_tan (log (tan (+ (/ PI 4.0) (* 0.5 phi))))] [log_ratio (log (/ (+ 1.0 sp) (- 1.0 sp)))] [exponent (+ (* c (- log_tan (* hlf_e log_ratio))) K)]) (- (* 2.0 (atan (exp exponent))) (/ PI 2.0)))) (FPCore (phip cosp0 sinp0 lamp) :name "forward-phipp" (asin (- (* cosp0 (sin phip)) (* sinp0 (* (cos phip) (cos lamp)))))) (FPCore (cp lamp phipp) :name "forward-lampp" (asin (/ (* cp (sin lamp)) (cos phipp)))) (FPCore (kR phipp) :name "forward-y" :pre (> kR 0.0) (* kR (log (tan (+ (/ PI 4.0) (* 0.5 phipp)))))) (FPCore (y kR) :name "inverse-phipp" :pre (> kR 0.0) (* 2.0 (- (atan (exp (/ y kR))) (/ PI 4.0)))) (FPCore (phipp cosp0 sinp0 lampp) :name "inverse-phip" (asin (+ (* cosp0 (sin phipp)) (* sinp0 (* (cos phipp) (cos lampp)))))) (FPCore (phipp lampp phip) :name "inverse-lamp" (asin (/ (* (cos phipp) (sin lampp)) (cos phip)))) (FPCore (K c phip) :name "inverse-con" (/ (- K (log (tan (+ (/ PI 4.0) (* 0.5 phip))))) c)) (FPCore (phip ecc) :name "inverse-esp" :pre (and (> ecc 0.0) (< ecc 1.0)) (* ecc (sin phip))) (FPCore (phip ecc hlf_e con rone_es) :name "inverse-delp" :pre (and (> ecc 0.0) (< ecc 1.0)) (let* ([esp (* ecc (sin phip))] [log_tan (log (tan (+ (/ PI 4.0) (* 0.5 phip))))] [log_ratio (log (/ (+ 1.0 esp) (- 1.0 esp)))] [term (- (+ con log_tan) (* hlf_e log_ratio))] [factor (* (- 1.0 (* esp esp)) (* (cos phip) rone_es))]) (* term factor))) (FPCore (phi0 es rone_es) :name "init-c" :pre (and (>= es 0.0) (< es 1.0) (> rone_es 0.0)) (let* ([cosphi0 (cos phi0)] [cos2 (* cosphi0 cosphi0)] [cos4 (* cos2 cos2)]) (sqrt (+ 1.0 (* es (* cos4 rone_es)))))) (FPCore (phi0 c) :name "init-sinp0" (/ (sin phi0) c)) (FPCore (phi0 phip0 c hlf_e ecc) :name "init-K" :pre (and (> c 0.0) (> ecc 0.0) (< ecc 1.0)) (let* ([sp (* ecc (sin phi0))] [log_tan_phi (log (tan (+ (/ PI 4.0) (* 0.5 phi0))))] [log_tan_phip0 (log (tan (+ (/ PI 4.0) (* 0.5 phip0))))] [log_ratio (log (/ (+ 1.0 sp) (- 1.0 sp)))]) (- log_tan_phip0 (* c (- log_tan_phi (* hlf_e log_ratio)))))) (FPCore (k0 one_es ecc phi0) :name "init-kR" :pre (and (> k0 0.0) (> one_es 0.0) (> ecc 0.0) (< ecc 1.0)) (let ([sp (* ecc (sin phi0))]) (/ (* k0 (sqrt one_es)) (- 1.0 (* sp sp))))) ================================================ FILE: bench/proj/tmerc.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (phi) :name "approx-t-guarded" (let ([sinphi (sin phi)] [cosphi (cos phi)]) (if (> (fabs cosphi) 1e-10) (/ sinphi cosphi) 0.0))) (FPCore (phi lam es) :name "approx-scaled-lambda" :pre (and (>= es 0.0) (< es 1.0)) (let* ([sinphi (sin phi)] [cosphi (cos phi)] [den (- 1.0 (* es (* sinphi sinphi)))]) (/ (* cosphi lam) (sqrt den)))) (FPCore (phi x es k0) :name "approx-inv-d-term" :pre (and (>= es 0.0) (< es 1.0) (> k0 0.0)) (let* ([sinphi (sin phi)] [con (- 1.0 (* es (* sinphi sinphi)))]) (/ (* x (sqrt con)) k0))) (FPCore (phi lam) :name "spherical-fwd-b" (let ([cosphi (cos phi)] [sinlam (sin lam)]) (* cosphi sinlam))) (FPCore (b ml0) :name "spherical-fwd-x-from-b" (* ml0 (log (/ (+ 1.0 b) (- 1.0 b))))) (FPCore (phi lam) :name "spherical-fwd-y-cos-quotient" (let* ([cosphi (cos phi)] [coslam (cos lam)] [b (* cosphi (sin lam))] [one-minus-b2 (- 1.0 (* b b))]) (/ (* cosphi coslam) (sqrt one-minus-b2)))) (FPCore (x esp) :name "spherical-inv-g-from-exp" :pre (> esp 0.0) (let ([h (exp (/ x esp))]) (* 0.5 (- h (/ 1.0 h))))) (FPCore (y esp phi0 g) :name "spherical-inv-latitude-angle" :pre (> esp 0.0) (let* ([D (+ phi0 (/ y esp))] [cosD (cos D)] [numer (- 1.0 (* cosD cosD))] [denom (+ 1.0 (* g g))]) (* (copysign 1.0 D) (asin (sqrt (/ numer denom)))))) (FPCore (y esp phi0 g) :name "spherical-inv-longitude" :pre (> esp 0.0) (let* ([D (+ phi0 (/ y esp))] [cD (cos D)]) (if (or (> (fabs g) 0.0) (> (fabs cD) 0.0)) (atan2 g cD) 0.0))) (FPCore (Cn lam) :name "exact-forward-conformal-lat" (let ([sC (sin Cn)] [cC (cos Cn)] [cE (cos lam)]) (atan2 sC (* cC cE)))) (FPCore (Cn lam) :name "exact-forward-tanCe" (let* ([sC (sin Cn)] [cC (cos Cn)] [sE (sin lam)] [cE (cos lam)] [den (sqrt (+ (* sC sC) (* cC (* cC (* cE cE)))))]) (/ (* sE cC) den))) (FPCore (Cn lam) :name "exact-forward-two-inv-denom" (let* ([sC (sin Cn)] [cC (cos Cn)] [cE (cos lam)] [den (sqrt (+ (* sC sC) (* cC (* cC (* cE cE)))))]) (/ 2.0 den))) (FPCore (sin_arg_r cosh_arg_i cos_arg_r sinh_arg_i hr hi) :name "clenshaw-final-real" (let ([r (* sin_arg_r cosh_arg_i)] [i (* cos_arg_r sinh_arg_i)]) (- (* r hr) (* i hi)))) (FPCore (sin_arg_r cosh_arg_i cos_arg_r sinh_arg_i hr hi) :name "clenshaw-final-imag" (let ([r (* sin_arg_r cosh_arg_i)] [i (* cos_arg_r sinh_arg_i)]) (+ (* r hi) (* i hr)))) (FPCore (Ce) :name "exact-inv-exp2Ce" (exp (* 2.0 Ce))) (FPCore (Ce) :name "exact-inv-half-inv-exp" (let ([exp2-val (exp (* 2.0 Ce))]) (/ 0.5 exp2-val))) (FPCore (Ce) :name "exact-inv-cosh-arg" (let* ([exp2-val (exp (* 2.0 Ce))] [half (/ 0.5 exp2-val)]) (+ (* 0.5 exp2-val) half))) (FPCore (Cn Ce) :name "exact-inv-Ce-angle" (let* ([sinhCe (sinh Ce)] [cosC (cos Cn)]) (atan2 sinhCe cosC))) (FPCore (Cn Ce) :name "exact-inv-Cn-angle" (let* ([sinhCe (sinh Ce)] [cosC (cos Cn)] [sinC (sin Cn)] [mod (sqrt (+ (* sinhCe sinhCe) (* cosC cosC)))]) (atan2 sinC mod))) (FPCore (Cn Ce) :name "exact-inv-rr" (let* ([sinhCe (sinh Ce)] [cosC (cos Cn)] [sinC (sin Cn)] [mod (sqrt (+ (* sinhCe sinhCe) (* cosC cosC)))]) (sqrt (+ (* sinC sinC) (* mod mod))))) ================================================ FILE: bench/regression.fpcore ================================================ ; -*- mode: scheme -*- ; This is a cool example that fails in cases of overflow (FPCore (lo hi x) :name "xlohi (overflows)" :pre (and (< lo -1e308) (> hi 1e308)) :precision binary64 (/ (- x lo) (- hi lo))) ; These once crashed Herbie (FPCore (x y z a) :name "tan-example (used to crash)" :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 c s) :name "mixedcos" (/ (cos (* 2 x)) (* (pow c 2) (* (* x (pow s 2)) x)))) (FPCore (x) :name "x (used to be hard to sample)" :pre (or (== x 0) (== x 10)) x) ; These should yield the same result (FPCore (r a b) :name "rsin A (should all be same)" (/ (* r (sin b)) (cos (+ a b)))) (FPCore (r a b) :name "rsin B (should all be same)" (* r (/ (sin b) (cos (+ a b))))) ; These should yield the same result (FPCore (x) :name "sqrt A (should all be same)" (sqrt (+ (* x x) (* x x)))) (FPCore (x) :name "sqrt B (should all be same)" (sqrt (* (* 2 x) x))) (FPCore (x) :name "sqrt C (should all be same)" (sqrt (* 2 (* x x)))) (FPCore (x) :name "sqrt D (should all be same)" (sqrt (* 2 (pow x 2)))) (FPCore (x) :name "sqrt E (should all be same)" (sqrt (+ (pow x 2) (pow x 2)))) ; This used to cause crashes (FPCore (w l) :name "exp-w (used to crash)" (* (exp (- w)) (pow l (exp w)))) (FPCore (x) :name "expfmod (used to be hard to sample)" (* (fmod (exp x) (sqrt (cos x))) (exp (- x)))) ; Regression tests from user-filed bugs (FPCore (x) :name "bug323 (missed optimization)" :pre (<= 0 x 0.5) :alt (! :herbie-platform default (* 2 (asin (sqrt (/ x 2))))) (acos (- 1 x))) (FPCore (x y) :name "bug329 (missed optimization)" :pre (>= x 0) :alt (! :herbie-platform default (atan2 y x)) (atan (/ y x))) (FPCore (x) :name "bug333 (missed optimization)" :pre (<= -1 x 1) :alt (! :herbie-platform default (/ (* 2 x) (+ (sqrt (+ 1 x)) (sqrt (- 1 x))))) (- (sqrt (+ 1 x)) (sqrt (- 1 x)))) (FPCore (x y z) :name "bug366 (missed optimization)" :alt (! :herbie-platform default (hypot x (hypot y z))) (sqrt (+ (* x x) (+ (* y y) (* z z))))) (FPCore (a b) :name "bug366, discussion (missed optimization)" :alt (! :herbie-platform default (let* ([fa (fabs a)] [fb (fabs b)]) (* (sqrt (+ fa fb)) (sqrt (- fa fb))))) (sqrt (- (* a a) (* b b)))) (FPCore (x) :name "bug500 (missed optimization)" :pre (< -1e3 x 1e3) :alt (! :herbie-platform default (if (< (fabs x) 0.07) (- (+ (- (/ (pow x 3) 6) (/ (pow x 5) 120)) (/ (pow x 7) 5040))) (- (sin x) x))) (- (sin x) x)) (FPCore (x) :name "bug500, discussion (missed optimization)" :alt (! :herbie-platform default (if (< (fabs x) .085) (let ([x2 (* x x)]) (* x2 (fma (fma (fma -1/37800 x2 1/2835) x2 -1/180) x2 1/6))) (log (/ (sinh x) x)))) (log (/ (sinh x) x))) (FPCore (x) :name "bug1188 (missed optimization)" :alt (! :herbie-platform default (if (>= (fabs x) (pow 2 52)) 0.0 (sin (* PI x)))) (sin (* PI x))) ================================================ FILE: bench/tutorial.fpcore ================================================ ; -*- mode: scheme -*- (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)))) ================================================ FILE: egg-herbie/.gitignore ================================================ target **/*.rs.bk dots/ egg/data/ # nix stuff .envrc default.nix # Racket *.zo *.dep ================================================ FILE: egg-herbie/Cargo.toml ================================================ [package] name = "egg-herbie" version = "0.3.0" authors = [ "Oliver Flatt ", "Max Willsey " ] edition = "2021" [dependencies] egg = { git = "https://github.com/egraphs-good/egg.git", rev = "6f2ac1ee72d0ec6b51d12c9ca59e94ada6615e87" } log = "0.4" indexmap = "1" libc = "0.2.125" num-bigint = "0.4.3" num-integer = "0.1.45" num-rational = "0.4.0" num-traits = "0.2.15" env_logger = { version = "0.9", default-features = false } [lib] name = "egg_math" crate-type = ["rlib", "cdylib"] [profile.test] debug = true opt-level = 1 [profile.release] debug = true lto = "fat" codegen-units = 1 ================================================ FILE: egg-herbie/LICENSE ================================================ Copyright 2019 Max Willsey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: egg-herbie/README.md ================================================ # Herbie's FFI to egraphs-good/egg This is a small Rust+Racket package co-developed with Herbie so that Herbie can bind to and use the [egg](https://egraphs-good.github.io) equality saturation library. The Racket side is in [`main.rkt`](main.rkt). It does only two things: find the `libegg_math` shared library and expose its main functions to Racket. It has a bit of logic for detecting architecture mismatches due to Rosetta Apple Silicon. The Rust side is implemented in standard Rust using the `egg` library. The `math` module contains math-specific implementation work while the `lib` module contains code to interface with Racket. The main Herbie repository's Github Actions build and publish versions of the Racket package (including pre-built Rust libraries). ================================================ FILE: egg-herbie/info.rkt ================================================ #lang info (define collection "egg-herbie") (define version "2.2") (define pkg-desc "Racket bindings for simplifying math expressions using egg") (define pkg-authors `("Oliver Flatt" "Brett Saiki" "Pavel Panchekha")) (define build-deps '("rackunit-lib")) (define deps '(("base" #:version "8.0"))) (define license 'MIT) ================================================ FILE: egg-herbie/main.rkt ================================================ #lang racket (require ffi/unsafe ffi/unsafe/define ffi/vector racket/runtime-path) (provide egraph_create egraph_destroy egraph_add_expr egraph_add_root egraph_add_node egraph_run egraph_copy egraph_get_stop_reason egraph_find egraph_serialize egraph_get_eclasses egraph_get_eclass egraph_get_simplest egraph_get_variants egraph_get_cost egraph_is_unsound_detected egraph_get_times_applied egraph_get_proof (struct-out iteration-data) (struct-out FFIRule) make-ffi-rule) (define-runtime-path libeggmath-path (build-path "target/release" (string-append (case (system-type) [(windows) "egg_math"] [else "libegg_math"]) (bytes->string/utf-8 (system-type 'so-suffix))))) ; Checks if Racket is being run in emulation via rosetta (define (running-on-rosetta?) (and (equal? (system-type 'os) 'macosx) (equal? (system-type 'arch) 'x86_64) (equal? (with-output-to-string (lambda () ; returns true if running in emulation (system "sysctl -n sysctl.proc_translated"))) "1\n"))) ; Note, this message should not be reached. (define fallback-message (string-append "Error: unable to load the 'egg-math' library\n" "Please file a bug at https://github.com/herbie-fp/herbie/issues")) ; Note, refering to ARM as Apple Silicon to match Racket download page. (define rosetta-message (string-append "Error: You are running the 'x86' version of Racket via 'Rosetta' emulation.\n" " Please use the 'Apple Silicon' version of Racket instead.\n" "You can install it from https://download.racket-lang.org")) (define (handle-eggmath-import-failure) (define error-message (if (running-on-rosetta?) rosetta-message fallback-message)) (raise-user-error error-message)) ; Checks for a condition on MacOS if x86 Racket is being used on an ARM mac. (define-ffi-definer define-eggmath (ffi-lib libeggmath-path #:fail handle-eggmath-import-failure)) ;; Frees a Rust-allocated C-string (define-eggmath destroy_string (_fun _pointer -> _void)) ;; Gets the length of a Rust-allocated C-string in bytes, ;; excluding the nul terminator. (define-eggmath string_length (_fun _pointer -> _uint32)) ;; Converts a Racket string to a C-style string. (define (string->_rust/string s #:raw? [raw? #f]) (define bstr (string->bytes/utf-8 s)) (define n (bytes-length bstr)) (define p (malloc (if raw? 'raw 'atomic) (add1 n))) (memcpy p bstr n) (ptr-set! p _byte n 0) p) ;; Converts a non-NULL, Rust-allocated C-string to a Racket string, ;; freeing the Rust string. (define (_rust/string->string p) (define len (string_length p)) (define bstr (make-bytes len)) (memcpy bstr p len) (destroy_string p) (bytes->string/utf-8 bstr)) ;; Converts a non-NULL, Rust-allocated C-string to a Racket datum ;; by repeatedly reading the string. The underlying Rust string ;; is automatically freed. (define (_rust/string->data p) (define len (string_length p)) (define bstr (make-bytes len)) (memcpy bstr p len) (destroy_string p) (for/list ([datum (in-port read (open-input-bytes bstr))]) datum)) ;; FFI type that converts Rust-allocated C-style strings ;; to Racket strings, automatically freeing the Rust-side allocation. (define _rust/string (make-ctype _pointer (lambda (x) (and x (string->_rust/string x))) (lambda (x) (and x (_rust/string->string x))))) ;; FFI type that converts Rust-allocated C-style strings ;; to a Racket datum via `read`, automatically freeing the Rust-side allocation. (define _rust/datum (make-ctype _pointer (lambda (x) (and x (string->_rust/string (~a x)))) (lambda (x) (and x (first (_rust/string->data x)))))) ;; FFI type that converts Rust-allocated C-style strings ;; to multiple Racket datum via reapeated use of `read`, ;; automatically freeing the Rust-side allocation. (define _rust/data (make-ctype _pointer (lambda (_) (error '_rust/data "cannot be used as an input type")) (lambda (x) (and x (_rust/string->data x))))) ; Egraph iteration data ; Not managed by Racket GC. ; Must call `destroy_egraphiters` to free. (define-cstruct _EGraphIter ([numnodes _uint] [numeclasses _uint] [time _double]) #:malloc-mode 'raw) ;; Frees an array of _EgraphIter structs (define-eggmath destroy_egraphiters (_fun _pointer -> _void)) ;; Racket representation of `_EGraphIter` (struct iteration-data (num-nodes num-eclasses time)) ;; Rewrite rule that can be passed over the FFI boundary. ;; Must be manually freed. (define-cstruct _FFIRule ([name _pointer] [left _pointer] [right _pointer]) #:malloc-mode 'raw) ;; Constructs for `_FFIRule` struct. (define (make-ffi-rule name lhs rhs) (define name* (string->_rust/string (~a name) #:raw? #t)) (define lhs* (string->_rust/string (~a lhs) #:raw? #t)) (define rhs* (string->_rust/string (~a rhs) #:raw? #t)) (define p (make-FFIRule name* lhs* rhs*)) (register-finalizer p free-ffi-rule) p) ;; Frees a `_FFIRule` struct. (define (free-ffi-rule rule) (free (FFIRule-name rule)) (free (FFIRule-left rule)) (free (FFIRule-right rule)) (free rule)) ; GC'able egraph ; If Racket GC can prove unreachable, `egraph_destroy` will be called (define _egraph-pointer (_cpointer 'egraph #f #f (lambda (p) (register-finalizer p egraph_destroy) p))) ;; Constructs an e-graph instances. (define-eggmath egraph_create (_fun -> _egraph-pointer)) ;; Frees an e-graph instance. (define-eggmath egraph_destroy (_fun _egraph-pointer -> _void)) ;; Copies an e-graph instance. (define-eggmath egraph_copy (_fun _egraph-pointer -> _egraph-pointer)) ;; Adds an expression to the e-graph. ;; egraph -> expr -> id (define-eggmath egraph_add_expr (_fun _egraph-pointer _rust/datum -> _uint)) (define-eggmath egraph_add_root (_fun _egraph-pointer _uint -> _void)) ; egraph -> string -> ids -> bool -> id (define-eggmath egraph_add_node (_fun [p : _egraph-pointer] ; egraph [f : _rust/string] ; enode op [v : _u32vector] ; id vector [_uint = (u32vector-length v)] ; id vector length -> _uint)) (define-eggmath egraph_is_unsound_detected (_fun _egraph-pointer -> _stdbool)) ;; Runs the egraph with a set of rules, returning the statistics of the run. (define-eggmath egraph_run (_fun _egraph-pointer ;; egraph (ffi-rules : (_list i _FFIRule-pointer)) ;; ffi rules (_uint = (length ffi-rules)) ;; number of rules (iterations-length : (_ptr o _uint)) ;; pointer to length of resulting array (iterations-ptr : (_ptr o _pointer)) ;; pointer to array allocation, caller frees _uint ;; iter limit _uint ;; node limit _stdbool ;; simple scheduler? -> (iterations : _EGraphIter-pointer) ;; array of _EgraphIter structs -> (begin (define iter-data (for/list ([i (in-range iterations-length)]) (define ptr (ptr-add iterations i _EGraphIter)) (iteration-data (EGraphIter-numnodes ptr) (EGraphIter-numeclasses ptr) (EGraphIter-time ptr)))) (destroy_egraphiters iterations-ptr) iter-data))) (define _stop_reason (_enum '(saturated iter-limit node-limit unsound) _uint)) ;; gets the stop reason as an integer (define-eggmath egraph_get_stop_reason (_fun _egraph-pointer -> _stop_reason)) ;; egraph -> string (define-eggmath egraph_serialize (_fun _egraph-pointer -> _rust/datum)) ;; egraph -> uint (define-eggmath egraph_size (_fun _egraph-pointer -> _uint)) ;; egraph -> id -> uint (define-eggmath egraph_eclass_size (_fun _egraph-pointer _uint -> _uint)) ;; egraph -> id -> idx -> uint (define-eggmath egraph_enode_size (_fun _egraph-pointer _uint _uint -> _uint)) ;; egraph -> u32vector (define-eggmath egraph_get_eclasses (_fun [e : _egraph-pointer] [v : _u32vector = (make-u32vector (egraph_size e))] -> _void -> v)) ;; egraph -> id -> u32 -> (or symbol? number? (cons symbol u32vector)) ;; UNSAFE: `v` must be large enough to contain the child ids (define-eggmath egraph_get_node (_fun [e : _egraph-pointer] [id : _uint32] [idx : _uint32] [v : _u32vector] -> [f : _rust/string] -> (if (zero? (u32vector-length v)) (or (string->number f) (string->symbol f)) (cons (string->symbol f) v)))) ; u32vector (define empty-u32vec (make-u32vector 0)) ; egraph -> id -> (vectorof (or symbol? number? (cons symbol u32vector))) (define (egraph_get_eclass egg-ptr id) (define n (egraph_eclass_size egg-ptr id)) (for/vector #:length n ([i (in-range n)]) (define node-size (egraph_enode_size egg-ptr id i)) (if (zero? node-size) (egraph_get_node egg-ptr id i empty-u32vec) (egraph_get_node egg-ptr id i (make-u32vector node-size))))) ;; egraph -> id -> id (define-eggmath egraph_find (_fun _egraph-pointer _uint -> _uint)) ;; egraph -> id -> (listof expr) (define-eggmath egraph_get_simplest (_fun _egraph-pointer _uint ;; node id _uint ;; iteration -> _rust/datum)) ;; expr ;; egraph -> id -> string -> (listof expr) (define-eggmath egraph_get_variants (_fun _egraph-pointer _uint ;; node id _rust/datum ;; original expr -> _rust/data)) ;; listof expr ;; egraph -> string -> string -> string ;; TODO: in Herbie, we bail on converting the proof ;; if the string is too big. It would be more efficient ;; to bail here instead. (define-eggmath egraph_get_proof (_fun _egraph-pointer ;; egraph _rust/datum ;; expr1 _rust/datum ;; expr2 -> _rust/string)) ;; string (define-eggmath egraph_get_cost (_fun _egraph-pointer _uint ;; node id _uint ;; iteration -> _uint)) (define-eggmath egraph_get_times_applied (_fun _egraph-pointer _pointer ;; name of the rule -> _uint)) ================================================ FILE: egg-herbie/src/lib.rs ================================================ #![allow(clippy::missing_safety_doc)] pub mod math; use egg::{BackoffScheduler, Extractor, FromOp, Id, Language, SimpleScheduler, StopReason, Symbol}; use indexmap::IndexMap; use libc::{c_void, strlen}; use math::*; use std::cmp::min; use std::ffi::{CStr, CString}; use std::mem::{self, ManuallyDrop}; use std::os::raw::c_char; use std::time::Duration; use std::{slice, sync::atomic::Ordering}; pub struct Context { iteration: usize, runner: Runner, rules: Vec, } // I had to add $(rustc --print sysroot)/lib to LD_LIBRARY_PATH to get linking to work after installing rust with rustup #[no_mangle] pub unsafe extern "C" fn egraph_create() -> *mut Context { Box::into_raw(Box::new(Context { iteration: 0, runner: Runner::new(Default::default()).with_explanations_enabled(), rules: vec![], })) } #[no_mangle] pub unsafe extern "C" fn egraph_destroy(ptr: *mut Context) { drop(Box::from_raw(ptr)) } #[no_mangle] pub unsafe extern "C" fn destroy_egraphiters(ptr: *mut c_void) { // TODO: Switch ffi to use `usize` directly to avoid the risk of these being incorrect drop(Box::from_raw(ptr as *mut Vec)); // drop(Vec::from_raw_parts(data, length as usize, capacity as usize)) } #[no_mangle] pub unsafe extern "C" fn destroy_string(ptr: *mut c_char) { drop(CString::from_raw(ptr)) } #[no_mangle] pub unsafe extern "C" fn string_length(ptr: *const c_char) -> u32 { strlen(ptr) as u32 } #[repr(C)] pub struct EGraphIter { numnodes: u32, numclasses: u32, time: f64, } // a struct for loading rules from external source #[repr(C)] pub struct FFIRule { name: *const c_char, left: *const c_char, right: *const c_char, } #[no_mangle] pub unsafe extern "C" fn egraph_add_expr(ptr: *mut Context, expr: *const c_char) -> u32 { let _ = env_logger::try_init(); // Safety: `ptr` was box allocated by `egraph_create` let mut context = Box::from_raw(ptr); assert_eq!(context.iteration, 0); let rec_expr = CStr::from_ptr(expr).to_str().unwrap().parse().unwrap(); context.runner = context.runner.with_expr(&rec_expr); let id = usize::from(*context.runner.roots.last().unwrap()) .try_into() .unwrap(); mem::forget(context); id } #[no_mangle] pub unsafe extern "C" fn egraph_add_root(ptr: *mut Context, id: u32) { let mut context = ManuallyDrop::new(Box::from_raw(ptr)); context.runner.roots.push(Id::from(id as usize)); } #[no_mangle] pub unsafe extern "C" fn egraph_add_node( ptr: *mut Context, f: *const c_char, ids_ptr: *const u32, num_ids: u32, ) -> u32 { let _ = env_logger::try_init(); // Safety: `ptr` was box allocated by `egraph_create` let mut context = ManuallyDrop::new(Box::from_raw(ptr)); let f = CStr::from_ptr(f).to_str().unwrap(); let len = num_ids as usize; let ids: &[u32] = slice::from_raw_parts(ids_ptr, len); let ids = ids.iter().map(|id| Id::from(*id as usize)).collect(); let node = Math::from_op(f, ids).unwrap(); let id = context.runner.egraph.add(node); usize::from(id) as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_copy(ptr: *mut Context) -> *mut Context { // Safety: `ptr` was box allocated by `egraph_create` let context = Box::from_raw(ptr); let mut runner = Runner::new(Default::default()) .with_explanations_enabled() .with_egraph(context.runner.egraph.clone()); runner.roots = context.runner.roots.clone(); runner.egraph.rebuild(); mem::forget(context); Box::into_raw(Box::new(Context { iteration: 0, rules: vec![], runner, })) } unsafe fn ptr_to_string(ptr: *const c_char) -> String { let bytes = CStr::from_ptr(ptr).to_bytes(); String::from_utf8(bytes.to_vec()).unwrap() } // todo don't just unwrap, also make sure the rules are validly parsed unsafe fn ffirule_to_tuple(rule_ptr: *mut FFIRule) -> (String, String, String) { let rule = &mut *rule_ptr; ( ptr_to_string(rule.name), ptr_to_string(rule.left), ptr_to_string(rule.right), ) } #[no_mangle] pub unsafe extern "C" fn egraph_run( ptr: *mut Context, rules_array_ptr: *const *mut FFIRule, rules_array_length: u32, iterations_length: *mut u32, iterations_ptr: *mut *mut c_void, iter_limit: u32, node_limit: u32, simple_scheduler: bool, ) -> *const EGraphIter { // Safety: `ptr` was box allocated by `egraph_create` let mut context = Box::from_raw(ptr); if context.runner.stop_reason.is_none() { let length: usize = rules_array_length as usize; let ffi_rules: &[*mut FFIRule] = slice::from_raw_parts(rules_array_ptr, length); let mut ffi_tuples: Vec<(&str, &str, &str)> = vec![]; let mut ffi_strings: Vec<(String, String, String)> = vec![]; for ffi_rule in ffi_rules.iter() { let str_tuple = ffirule_to_tuple(*ffi_rule); ffi_strings.push(str_tuple); } for ffi_string in ffi_strings.iter() { ffi_tuples.push((&ffi_string.0, &ffi_string.1, &ffi_string.2)); } let rules: Vec = math::mk_rules(&ffi_tuples); context.rules = rules; context.runner = if simple_scheduler { context.runner.with_scheduler(SimpleScheduler) } else { context.runner.with_scheduler(BackoffScheduler::default()) }; context.runner = context .runner .with_node_limit(node_limit as usize) .with_iter_limit(iter_limit as usize) // should never hit .with_time_limit(Duration::from_secs(u64::MAX)) .with_hook(|r| { if r.egraph.analysis.unsound.load(Ordering::SeqCst) { Err("Unsoundness detected".into()) } else { Ok(()) } }) .run(&context.rules); } // Prune all e-nodes with children where its e-class has a leaf node (with no children). Pruning // safely improves performance because pruning occurs right before extraction and leaf e-nodes // always have a lower cost. context.runner.egraph.classes_mut().for_each(|eclass| { if eclass.nodes.iter().any(|n| n.is_leaf()) { eclass.nodes.retain(|n| n.is_leaf()); } }); let iterations = context .runner .iterations .iter() .map(|iteration| EGraphIter { numnodes: iteration.egraph_nodes as u32, numclasses: iteration.egraph_classes as u32, time: iteration.total_time, }) .collect::>(); let iterations_data = iterations.as_ptr(); std::ptr::write(iterations_length, iterations.len() as u32); std::ptr::write( iterations_ptr, Box::into_raw(Box::new(iterations)) as *mut c_void, ); mem::forget(context); iterations_data } #[no_mangle] pub unsafe extern "C" fn egraph_get_stop_reason(ptr: *mut Context) -> u32 { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); match context.runner.stop_reason { Some(StopReason::Saturated) => 0, Some(StopReason::IterationLimit(_)) => 1, Some(StopReason::NodeLimit(_)) => 2, Some(StopReason::Other(_)) => 3, _ => 4, } } fn find_extracted(runner: &Runner, id: u32, iter: u32) -> &Extracted { let id = runner.egraph.find(Id::from(id as usize)); // go back one more iter, egg can duplicate the final iter in the case of an error let is_unsound = runner.egraph.analysis.unsound.load(Ordering::SeqCst); let sound_iter = min( runner .iterations .len() .saturating_sub(if is_unsound { 3 } else { 1 }), iter as usize, ); runner.iterations[sound_iter] .data .extracted .iter() .find(|(i, _)| runner.egraph.find(*i) == id) .map(|(_, ext)| ext) .expect("Couldn't find matching extraction!") } #[no_mangle] pub unsafe extern "C" fn egraph_find(ptr: *mut Context, id: usize) -> u32 { let context = ManuallyDrop::new(Box::from_raw(ptr)); let node_id = Id::from(id); let canon_id = context.runner.egraph.find(node_id); usize::from(canon_id) as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_serialize(ptr: *mut Context) -> *const c_char { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); let mut ids: Vec = context.runner.egraph.classes().map(|c| c.id).collect(); ids.sort(); // Iterate through the eclasses and print each eclass let mut s = String::from("("); for id in ids { let c = &context.runner.egraph[id]; s.push_str(&format!("({}", id)); for node in &c.nodes { if matches!(node, Math::Symbol(_) | Math::Constant(_)) { s.push_str(&format!(" {}", node)); } else { s.push_str(&format!("({}", node)); for c in node.children() { s.push_str(&format!(" {}", c)); } s.push(')'); } } s.push(')'); } s.push(')'); let c_string = ManuallyDrop::new(CString::new(s).unwrap()); c_string.as_ptr() } #[no_mangle] pub unsafe extern "C" fn egraph_size(ptr: *mut Context) -> u32 { let context = ManuallyDrop::new(Box::from_raw(ptr)); context.runner.egraph.number_of_classes() as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_eclass_size(ptr: *mut Context, id: u32) -> u32 { let context = ManuallyDrop::new(Box::from_raw(ptr)); let id = Id::from(id as usize); context.runner.egraph[id].nodes.len() as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_enode_size(ptr: *mut Context, id: u32, idx: u32) -> u32 { let context = ManuallyDrop::new(Box::from_raw(ptr)); let id = Id::from(id as usize); let idx = idx as usize; context.runner.egraph[id].nodes[idx].len() as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_get_eclasses(ptr: *mut Context, ids_ptr: *mut u32) { let context = ManuallyDrop::new(Box::from_raw(ptr)); let mut ids: Vec = context .runner .egraph .classes() .map(|c| usize::from(c.id) as u32) .collect(); ids.sort(); for (i, id) in ids.iter().enumerate() { std::ptr::write(ids_ptr.offset(i as isize), *id); } } #[no_mangle] pub unsafe extern "C" fn egraph_get_node( ptr: *mut Context, id: u32, idx: u32, ids: *mut u32, ) -> *const c_char { let context = ManuallyDrop::new(Box::from_raw(ptr)); let id = Id::from(id as usize); let idx = idx as usize; let node = &context.runner.egraph[id].nodes[idx]; for (i, id) in node.children().iter().enumerate() { std::ptr::write(ids.offset(i as isize), usize::from(*id) as u32); } let c_string = ManuallyDrop::new(CString::new(node.to_string()).unwrap()); c_string.as_ptr() } #[no_mangle] pub unsafe extern "C" fn egraph_get_simplest( ptr: *mut Context, node_id: u32, iter: u32, ) -> *const c_char { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); let ext = find_extracted(&context.runner, node_id, iter); let best_str = ManuallyDrop::new(CString::new(ext.best.to_string()).unwrap()); best_str.as_ptr() } #[no_mangle] pub unsafe extern "C" fn egraph_get_proof( ptr: *mut Context, expr: *const c_char, goal: *const c_char, ) -> *const c_char { // Safety: `ptr` was box allocated by `egraph_create` let mut context = ManuallyDrop::new(Box::from_raw(ptr)); // Send `EGraph` since neither `Context` nor `Runner` are `Send`. `Runner::explain_equivalence` just forwards to `EGraph::explain_equivalence` so this is fine. let egraph = &mut context.runner.egraph; let expr_rec = CStr::from_ptr(expr).to_str().unwrap().parse().unwrap(); let goal_rec = CStr::from_ptr(goal).to_str().unwrap().parse().unwrap(); // extract the proof as a tree let string = egraph .explain_equivalence(&expr_rec, &goal_rec) .get_string_with_let() .replace('\n', " "); let c_string = ManuallyDrop::new(CString::new(string).unwrap()); c_string.as_ptr() } #[no_mangle] pub unsafe extern "C" fn egraph_get_variants( ptr: *mut Context, node_id: u32, orig_expr: *const c_char, ) -> *const c_char { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); // root (id, expr) let id = Id::from(node_id as usize); let orig_recexpr: RecExpr = CStr::from_ptr(orig_expr).to_str().unwrap().parse().unwrap(); let head_node = &orig_recexpr.as_ref()[orig_recexpr.as_ref().len() - 1]; // extractor let extractor = Extractor::new(&context.runner.egraph, AltCost::new(&context.runner.egraph)); let mut cache: IndexMap = Default::default(); // extract variants let mut exprs = vec![]; for n in &context.runner.egraph[id].nodes { // assuming same ops in an eclass cannot // have different precisions if !n.matches(head_node) { // extract if not in cache n.for_each(|id| { if cache.get(&id).is_none() { let (_, best) = extractor.find_best(id); cache.insert(id, best); } }); exprs.push(n.join_recexprs(|id| cache.get(&id).unwrap().as_ref())); } } // format let expr_strs: Vec = exprs.iter().map(|r| r.to_string()).collect(); let best_str = ManuallyDrop::new(CString::new(expr_strs.join(" ")).unwrap()); best_str.as_ptr() } #[no_mangle] pub unsafe extern "C" fn egraph_is_unsound_detected(ptr: *mut Context) -> bool { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); context .runner .egraph .analysis .unsound .load(Ordering::SeqCst) } #[no_mangle] pub unsafe extern "C" fn egraph_get_times_applied(ptr: *mut Context, name: *const c_char) -> u32 { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); let sym = Symbol::from(ptr_to_string(name)); context .runner .iterations .iter() .map(|iter| *iter.applied.get(&sym).unwrap_or(&0) as u32) .sum() } #[no_mangle] pub unsafe extern "C" fn egraph_get_cost(ptr: *mut Context, node_id: u32, iter: u32) -> u32 { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); let ext = find_extracted(&context.runner, node_id, iter); ext.cost as u32 } #[no_mangle] pub unsafe extern "C" fn egraph_get_size(ptr: *mut Context) -> u32 { // Safety: `ptr` was box allocated by `egraph_create` let context = ManuallyDrop::new(Box::from_raw(ptr)); context .runner .iterations .last() .map(|iteration| iteration.egraph_nodes as u32) .unwrap_or_default() } ================================================ FILE: egg-herbie/src/math.rs ================================================ use egg::*; use std::sync::atomic::{AtomicBool, Ordering}; use num_bigint::BigInt; use num_integer::Integer; use num_rational::Ratio; use num_traits::{One, Pow, Signed, Zero}; use std::str::FromStr; pub type Constant = num_rational::BigRational; pub type RecExpr = egg::RecExpr; pub type Pattern = egg::Pattern; pub type EGraph = egg::EGraph; pub type Rewrite = egg::Rewrite; pub type Runner = egg::Runner; pub type Iteration = egg::Iteration; pub struct IterData { pub extracted: Vec<(Id, Extracted)>, } pub struct Extracted { pub best: RecExpr, pub cost: usize, } // cost function similar to AstSize except it will // penalize `(pow _ p)` where p is a fraction pub struct AltCost<'a> { pub egraph: &'a EGraph, } impl<'a> AltCost<'a> { pub fn new(egraph: &'a EGraph) -> Self { Self { egraph } } } impl<'a> CostFunction for AltCost<'a> { type Cost = usize; fn cost(&mut self, enode: &Math, mut costs: C) -> Self::Cost where C: FnMut(Id) -> Self::Cost, { if let Math::Pow([_, i]) = enode { if let Some((n, _reason)) = &self.egraph[*i].data { if !n.denom().is_one() && n.denom().is_odd() { return usize::MAX; } } } enode.fold(1, |sum, id| usize::saturating_add(sum, costs(id))) } } impl IterationData for IterData { fn make(runner: &Runner) -> Self { let extractor = Extractor::new(&runner.egraph, AltCost::new(&runner.egraph)); let extracted = runner .roots .iter() .map(|&root| { let (cost, best) = extractor.find_best(root); let ext = Extracted { cost, best }; (root, ext) }) .collect(); Self { extracted } } } // operators from FPCore define_language! { pub enum Math { // constant-folding operators "+" = Add([Id; 2]), "-" = Sub([Id; 2]), "*" = Mul([Id; 2]), "/" = Div([Id; 2]), "pow" = Pow([Id; 2]), "neg" = Neg([Id; 1]), "sqrt" = Sqrt([Id; 1]), "fabs" = Fabs([Id; 1]), "ceil" = Ceil([Id; 1]), "floor" = Floor([Id; 1]), "round" = Round([Id; 1]), "log" = Log([Id; 1]), "cbrt" = Cbrt([Id; 1]), Constant(Constant), Symbol(egg::Symbol), Other(egg::Symbol, Vec), } } pub struct ConstantFold { pub unsound: AtomicBool, pub max_abs_exponent: Ratio, pub prune: bool, } impl Clone for ConstantFold { fn clone(&self) -> Self { let unsound = AtomicBool::new(self.unsound.load(Ordering::SeqCst)); Self { unsound, max_abs_exponent: self.max_abs_exponent.clone(), prune: self.prune, } } } impl Default for ConstantFold { fn default() -> Self { Self { unsound: AtomicBool::new(false), // Avoid calculating extremely large numbers. 16 is somewhat arbitrary, even 0 passes // all tests. max_abs_exponent: Ratio::new(BigInt::from(16), BigInt::from(1)), prune: true, } } } impl Analysis for ConstantFold { type Data = Option<(Constant, (PatternAst, Subst))>; fn make(egraph: &mut EGraph, enode: &Math) -> Self::Data { let x = |id: &Id| egraph[*id].data.clone().map(|x| x.0); let is_zero = |id: &Id| { let data = egraph[*id].data.as_ref(); match data { Some(data) => data.0.is_zero(), None => false, } }; Some(( match enode { Math::Constant(c) => c.clone(), // real Math::Add([a, b]) => x(a)? + x(b)?, Math::Sub([a, b]) => x(a)? - x(b)?, Math::Mul([a, b]) => x(a)? * x(b)?, Math::Div([a, b]) => { if x(b)?.is_zero() { return None; } else { x(a)? / x(b)? } } Math::Neg([a]) => -x(a)?, Math::Pow([a, b]) => { if is_zero(a) { if x(b)?.is_positive() { Ratio::new(BigInt::from(0), BigInt::from(1)) } else { return None; } } else if is_zero(b) { Ratio::new(BigInt::from(1), BigInt::from(1)) } else if x(b)?.is_integer() && x(b)?.abs() <= egraph.analysis.max_abs_exponent { Pow::pow(x(a)?, x(b)?.to_integer()) } else { return None; } } Math::Sqrt([a]) => { let a = x(a)?; if *a.numer() > BigInt::from(0) && *a.denom() > BigInt::from(0) { let s1 = a.numer().sqrt(); let s2 = a.denom().sqrt(); let is_perfect = &(&s1 * &s1) == a.numer() && &(&s2 * &s2) == a.denom(); if is_perfect { Ratio::new(s1, s2) } else { return None; } } else { return None; } } Math::Log([a]) => { if x(a)? == Ratio::new(BigInt::from(1), BigInt::from(1)) { Ratio::new(BigInt::from(0), BigInt::from(1)) } else { return None; } } Math::Cbrt([a]) => { if x(a)? == Ratio::new(BigInt::from(1), BigInt::from(1)) { Ratio::new(BigInt::from(1), BigInt::from(1)) } else { return None; } } Math::Fabs([a]) => x(a)?.abs(), Math::Floor([a]) => x(a)?.floor(), Math::Ceil([a]) => x(a)?.ceil(), Math::Round([a]) => x(a)?.round(), _ => return None, }, { let mut pattern: PatternAst = Default::default(); let mut var_counter = 0; let mut subst: Subst = Default::default(); enode.for_each(|child| { if let Some(constant) = x(&child) { pattern.add(ENodeOrVar::ENode(Math::Constant(constant))); } else { let var = ("?".to_string() + &var_counter.to_string()) .parse() .unwrap(); pattern.add(ENodeOrVar::Var(var)); subst.insert(var, child); var_counter += 1; } }); let mut counter = 0; let mut head = enode.clone(); head.update_children(|_child| { let res = Id::from(counter); counter += 1; res }); pattern.add(ENodeOrVar::ENode(head)); (pattern, subst) }, )) } fn merge(&mut self, to: &mut Self::Data, from: Self::Data) -> DidMerge { match (&to, from) { (None, None) => DidMerge(false, false), (Some(_), None) => DidMerge(false, true), // no update needed (None, Some(c)) => { *to = Some(c); DidMerge(true, false) } (Some(a), Some(ref b)) => { if a.0 != b.0 && !self.unsound.swap(true, Ordering::SeqCst) { log::warn!("Bad merge detected: {} != {}", a.0, b.0); } DidMerge(false, false) } } } fn modify(egraph: &mut EGraph, class_id: Id) { let class = &mut egraph[class_id]; if let Some((c, (pat, subst))) = class.data.clone() { egraph.union_instantiations( &pat, &format!("{}", c).parse().unwrap(), &subst, "metadata-eval".to_string(), ); } } } pub fn mk_rules(tuples: &[(&str, &str, &str)]) -> Vec { tuples .iter() .map(|(name, left, right)| { let left = Pattern::from_str(left).unwrap(); let right = Pattern::from_str(right).unwrap(); Rewrite::new(*name, left, right).unwrap() }) .collect() } ================================================ FILE: infra/.gitignore ================================================ *.log *exceptions*.rkt graphs-*/ ================================================ FILE: infra/analyze-rules.rkt ================================================ #lang racket (require "../src/core/egg-herbie.rkt" "../src/syntax/batch.rkt" "../src/syntax/types.rkt" "../src/syntax/load-platform.rkt" "../src/syntax/platform.rkt" "../src/core/programs.rkt" "../src/core/rules.rkt" "../src/syntax/read.rkt") (define *iters* (make-parameter 3)) (module+ main (command-line #:once-each [("--iters") iters "How many iterations to analyze" (*iters* (string->number iters))] #:args ([dir "bench/"]) (run-analysis dir))) (define (run-analysis dir) (activate-platform! "c") (define tests (load-tests dir)) (printf "Loaded ~a tests from ~a\n" (length tests) dir) ;; Store accumulated impact: iter -> rule-name -> total-percentage (define impact (make-vector (*iters*))) (define iter-sizes (make-vector (*iters*) '())) ; iter -> list of final sizes (for ([i (in-range (*iters*))]) (vector-set! impact i (make-hash))) (for ([test (in-list tests)] [i (in-naturals)]) (printf "Processing test ~a/~a: ~a\n" (+ i 1) (length tests) (test-name test)) (define-values (batch brfs) (progs->batch (list (test-input test)))) (for ([iter (in-range (*iters*))]) (define-values (initial-size final-size sorted-results) (egraph-analyze-rewrite-impact batch brfs (test-context test) iter)) (vector-set! iter-sizes iter (cons final-size (vector-ref iter-sizes iter))) (for ([(rule delta) (in-dict sorted-results)]) (define pct (/ (* 100.0 delta) final-size)) (define iter-hash (vector-ref impact iter)) (hash-update! iter-hash (rule-name rule) (curry + pct) 0.0)))) (define count (length tests)) (for ([iter (in-range (*iters*))]) (define sizes (vector-ref iter-sizes iter)) (define log-sum (apply + (map log sizes))) (define geomean (exp (/ log-sum count))) (printf "=== Iteration ~a (Geomean: ~a) ===\n" iter (exact-floor geomean)) (define iter-impact (vector-ref impact iter)) ;; Average the percentages (define sorted-avg (sort (hash->list iter-impact) > #:key cdr)) (for ([item (in-list sorted-avg)] [_ (in-range 10)]) (printf "~a ~a%\n" (~a (car item) #:width 30) (~a (~r (/ (cdr item) count) #:precision '(= 2) #:min-width 6) #:width 7 #:align 'right))) (newline))) ================================================ FILE: infra/bench/posit-pherbie.fpcore ================================================ ; -*- mode: scheme -*- (FPCore (x) :name "2sqrt (example 3.1)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (sqrt (+ x 1)) (sqrt x))) (FPCore (x eps) :name "2sin (example 3.3)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (sin (+ x eps)) (sin x))) (FPCore (x) :name "tanhf (example 3.4)" :precision binary32 :herbie-conversions ((binary32 posit16)) (/ (- 1 (cos x)) (sin x))) (FPCore (N) :name "2atan (example 3.5)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (atan (+ N 1)) (atan N))) (FPCore (x) :name "2isqrt (example 3.6)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))) (FPCore (x) :name "2frac (problem 3.3.1)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (/ 1 (+ x 1)) (/ 1 x))) (FPCore (x eps) :name "2tan (problem 3.3.2)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (tan (+ x eps)) (tan x))) (FPCore (x) :name "3frac (problem 3.3.3)" :precision binary32 :herbie-conversions ((binary32 posit16)) (+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))) (FPCore (x) :name "2cbrt (problem 3.3.4)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (cbrt (+ x 1)) (cbrt x))) (FPCore (x eps) :name "2cos (problem 3.3.5)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (cos (+ x eps)) (cos x))) (FPCore (N) :name "2log (problem 3.3.6)" :precision binary32 :herbie-conversions ((binary32 posit16)) (- (log (+ N 1)) (log N))) (FPCore (x) :name "exp2 (problem 3.3.7)" :precision binary32 :herbie-conversions ((binary32 posit16)) (+ (- (exp x) 2) (exp (- x)))) ================================================ FILE: infra/bench/posits.fpcore ================================================ ; -*- mode: scheme -*- ; Herbie cannot properly sample points for this FPCore ; ; (FPCore (a b c) ; :pre (and (< 0 a) (< 0 b) (< 0 c)) ; :name "Area of a triangle" ; :precision posit16 ; :herbie-expected 16 ; (let ([s (/ (+ (+ a b) c) 2)]) ; (sqrt (* s (- s a) (- s b) (- s c))))) (FPCore (a b c) :name "quadp (p42, positive)" :precision posit16 :herbie-expected 16 (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (+ (- b) d) (* 2 a)))) (FPCore (a b c) :name "quadm (p42, negative)" :precision posit16 :herbie-expected 16 (let ([d (sqrt (- (* b b) (* 4 (* a c))))]) (/ (- (- b) d) (* 2 a)))) (FPCore (a b_2 c) :name "quad2m (problem 3.2.1, negative)" :precision posit16 :herbie-expected 16 (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) (/ (- (- b_2) d) a))) (FPCore (a b_2 c) :name "quad2p (problem 3.2.1, positive)" :precision posit16 :herbie-expected 16 (let ([d (sqrt (- (* b_2 b_2) (* a c)))]) (/ (+ (- b_2) d) a))) (FPCore (x) :name "2sqrt (example 3.1)" :precision posit16 :herbie-expected 16 (- (sqrt (+ x 1)) (sqrt x))) (FPCore (x) :name "2isqrt (example 3.6)" :precision posit16 :herbie-expected 16 (- (/ 1 (sqrt x)) (/ 1 (sqrt (+ x 1))))) (FPCore (x) :name "2frac (problem 3.3.1)" :precision posit16 :herbie-expected 16 (- (/ 1 (+ x 1)) (/ 1 x))) (FPCore (x) :name "3frac (problem 3.3.3)" :precision posit16 :herbie-expected 16 (+ (- (/ 1 (+ x 1)) (/ 2 x)) (/ 1 (- x 1)))) (FPCore (re im) :name "math.abs on complex" :precision posit16 :herbie-expected 16 (sqrt (+ (* re re) (* im im)))) (FPCore (x.re x.im) :name "math.cube on complex, real part" :precision posit16 :herbie-expected 16 (- (* (- (* x.re x.re) (* x.im x.im)) x.re) (* (+ (* x.re x.im) (* x.im x.re)) x.im))) (FPCore (x.re x.im) :name "math.cube on complex, imaginary part" :precision posit16 :herbie-expected 16 (+ (* (- (* x.re x.re) (* x.im x.im)) x.im) (* (+ (* x.re x.im) (* x.im x.re)) x.re))) (FPCore (x.re x.im y.re y.im) :name "_divideComplex, real part" :precision posit16 :herbie-expected 16 (/ (+ (* x.re y.re) (* x.im y.im)) (+ (* y.re y.re) (* y.im y.im)))) (FPCore (x.re x.im y.re y.im) :name "_divideComplex, imaginary part" :precision posit16 :herbie-expected 16 (/ (- (* x.im y.re) (* x.re y.im)) (+ (* y.re y.re) (* y.im y.im)))) (FPCore (x.re x.im y.re y.im) :name "_multiplyComplex, real part" :precision posit16 :herbie-expected 16 (- (* x.re y.re) (* x.im y.im))) (FPCore (x.re x.im y.re y.im) :name "_multiplyComplex, imaginary part" :precision posit16 :herbie-expected 16 (+ (* x.re y.im) (* x.im y.re))) (FPCore (re im) :name "math.sqrt on complex, real part" :precision posit16 :herbie-expected 16 (* 0.5 (sqrt (* 2.0 (+ (sqrt (+ (* re re) (* im im))) re))))) (FPCore (re im) :name "math.sqrt on complex, imaginary part, im greater than 0 branch" :precision posit16 :herbie-expected 16 (* 0.5 (sqrt (* 2.0 (- (sqrt (+ (* re re) (* im im))) re))))) (FPCore (re im) :name "math.square on complex, real part" :precision posit16 :herbie-expected 16 (- (* re re) (* im im))) (FPCore (re im) :name "math.square on complex, imaginary part" :precision posit16 :herbie-expected 16 (+ (* re im) (* im re))) (FPCore (alpha beta) :pre (and (> alpha -1) (> beta -1)) :name "Octave 3.8, jcobi/1" :precision posit16 :herbie-expected 16 (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (/ (+ (/ ad (+ ab 2.0)) 1.0) 2.0))) (FPCore (alpha beta i) :pre (and (> alpha -1) (> beta -1) (> i 0)) :name "Octave 3.8, jcobi/2" :precision posit16 :herbie-expected 16 (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z (+ ab (* 2 i)))) (/ (+ (/ (* ab ad) z (+ z 2.0)) 1.0) 2.0)))) (FPCore (alpha beta) :pre (and (> alpha -1) (> beta -1)) :name "Octave 3.8, jcobi/3" :precision posit16 :herbie-expected 16 (let ((i 1) (ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z1 i)) (let ((z (+ ab (* 2 z1)))) (/ (+ ab ap 1.0) z z (+ z 1.0)))))) (FPCore (alpha beta i) ; TODO: i should be an integer :pre (and (> alpha -1) (> beta -1) (> i 1)) :name "Octave 3.8, jcobi/4" :precision posit16 :herbie-expected 16 (let ((ab (+ alpha beta)) (ad (- beta alpha)) (ap (* beta alpha))) (let ((z (+ ab (* 2 i)))) (let ((z* (* z z)) (y (* i (+ ab i)))) (let ((y* (* y (+ ap y)))) (/ y* z* (- z* 1.0))))))) (FPCore (i) :pre (and (> i 0)) :name "Octave 3.8, jcobi/4, as called" :precision posit16 :herbie-expected 16 (let ((z (* 2 i))) (let ((z* (* z z)) (y (* i i))) (let ((y* (* y y))) (/ y* z* (- z* 1.0)))))) (FPCore (a rand) :name "Octave 3.8, oct_fill_randg" :precision posit16 :herbie-expected 16 (let ((d (- a (/ 1.0 3.0)))) (let ((c (/ 1 (sqrt (* 9 d)))) (x rand)) (let ((v (+ 1 (* c x)))) (let ((v* (* v (* v v))) (xsq (* x x))) (* d v)))))) (FPCore (d1 d2 d3) :name "FastMath dist" :precision posit16 :herbie-expected 16 (+ (* d1 d2) (* d1 d3))) (FPCore (d) :name "FastMath test1" :precision posit16 :herbie-expected 16 (+ (* d 10) (* d 20))) (FPCore (d1 d2) :name "FastMath test2" :precision posit16 :herbie-expected 16 (+ (* d1 10) (* d1 d2) (* d1 20))) (FPCore (d1 d2 d3) :name "FastMath dist3" :precision posit16 :herbie-expected 16 (+ (* d1 d2) (* (+ d3 5) d1) (* d1 32))) (FPCore (d1 d2 d3 d4) :name "FastMath dist4" :precision posit16 :herbie-expected 16 (- (+ (- (* d1 d2) (* d1 d3)) (* d4 d1)) (* d1 d1))) (FPCore (d1 d2 d3) :name "FastMath test3" :precision posit16 :herbie-expected 16 (+ (* d1 3) (* d1 d2) (* d1 d3))) (FPCore (d1) :name "FastMath repmul" :precision posit16 :herbie-expected 16 (* d1 d1 d1 d1)) (FPCore (x) :name "Jmat.Real.dawson" :precision posit16 :herbie-expected 16 (let ((p1 0.1049934947) (p2 0.0424060604) (p3 0.0072644182) (p4 0.0005064034) (p5 0.0001789971) (q1 0.7715471019) (q2 0.2909738639) (q3 0.0694555761) (q4 0.0140005442) (q5 0.0008327945)) (let ((x2 (* x x))) (let ((x4 (* x2 x2))) (let ((x6 (* x4 x2))) (let ((x8 (* x6 x2))) (let ((x10 (* x8 x2))) (let ((x12 (* x10 x2))) (* (/ (+ 1 (* p1 x2) (* p2 x4) (* p3 x6) (* p4 x8) (* p5 x10)) (+ 1 (* q1 x2) (* q2 x4) (* q3 x6) (* q4 x8) (* q5 x10) (* 2 p5 x12))) x))))))))) ================================================ FILE: infra/ci.rkt ================================================ #lang racket (require "../src/utils/common.rkt" "../src/core/points.rkt" "../src/core/alternative.rkt" "../src/api/sandbox.rkt" "../src/syntax/read.rkt" "../src/syntax/types.rkt" "../src/syntax/platform.rkt" "../src/syntax/load-platform.rkt") (define *precision* (make-parameter #f)) (define (test-successful? test input-bits target-bits output-bits) (match* ((test-output test) (test-expected test)) [(_ #f) #t] [(_ (? number? n)) (>= n output-bits)] [('() #t) (>= input-bits output-bits)] [(_ #t) (>= target-bits (- output-bits 1))])) (define (override-test-precision the-test repr) (struct-copy test the-test [output-repr-name (representation-name repr)] [var-repr-names (for/list ([var (in-list (test-vars the-test))]) (cons var (representation-name repr)))])) (define (run-tests . bench-dirs) (activate-platform! (*platform-name*)) (define tests (parameterize ([*default-precision* (*precision*)]) (append-map load-tests bench-dirs))) (define seed (pseudo-random-generator->vector (current-pseudo-random-generator))) (printf "Running Herbie on ~a tests, seed: ~a\n" (length tests) seed) (for/and ([the-test tests] [i (in-naturals)]) (printf "~a/~a\t" (~a (+ 1 i) #:width 3 #:align 'right) (length tests)) (define the-test* (if (*precision*) (override-test-precision the-test (get-representation (*precision*))) the-test)) (define result (run-herbie 'improve the-test* #:seed seed)) (match-define (job-result _ test status time timeline profile warnings backend) result) (match status ['success (match-define (improve-result _ start targets end) backend) (match-define (alt-analysis start-alt start-error) start) (match-define (alt-analysis end-alt end-error) (first end)) ;; Pick lowest target from all target (define target-error ; If the list is empty, return false (if (empty? targets) #f (argmin errors-score (map alt-analysis-errors targets)))) (printf "[ ~as] ~a→~a\t~a\n" (~r (/ time 1000) #:min-width 7 #:precision '(= 3)) (~r (errors-score start-error) #:min-width 2 #:precision 0) (~r (errors-score end-error) #:min-width 2 #:precision 0) (test-name test)) (define success? (test-successful? test (errors-score start-error) (and target-error (errors-score target-error)) (errors-score end-error))) (unless success? (printf "\nInput (~a bits):\n" (errors-score start-error)) (pretty-print (alt-expr start-alt) (current-output-port) 1) (printf "\nOutput (~a bits):\n" (errors-score end-error)) (pretty-print (alt-expr end-alt) (current-output-port) 1) (when target-error (printf "\nTarget (~a bits):\n" (errors-score target-error)) ;; internal tool so okay (pretty-print (list-ref (test-output test) 0) (current-output-port) 1))) success?] ['failure (define exn backend) (printf "[ CRASH ]\t\t~a\n" (test-name test)) ((error-display-handler) (exn-message exn) exn) #f] ['timeout (printf "[ TIMEOUT]\t\t~a\n" (test-name test)) #f]))) (module+ main ;; Load all the plugins (define seed (random 1 (expt 2 31))) (set-seed! seed) (command-line #:program "ci.rkt" #:once-each [("--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))] [("--egglog") "Switch to the egglog backend" (enable-flag! 'generate 'egglog)] [("--rival2") "Switch to the Rival 2 backend" (enable-flag! 'setup 'rival2)] [("--platform") platform "Which platform to use for tests" (*platform-name* platform)] [("--precision") prec "Which precision to use for tests" (*precision* (string->symbol prec))] [("--num-iters") num "The number of iterations to use for the main loop" (*num-iterations* (string->number num))] [("--timeout") s "Timeout per test in seconds" (*timeout* (* 1000 (string->number s)))] #:args bench-dir (exit (if (apply run-tests bench-dir) 0 1)))) ================================================ FILE: infra/convert-demo-json.sh ================================================ #!/bin/bash if [ "$#" -ne 1 ]; then echo "Usage: supply the directory which contains the demo json files (v10.json, ect)" echo "These files will be converted to fpcore and placed in bench/demosubmissions" exit 0 fi echo "Converting user submitted data into benchmark suite" if [ -d "bench/demosubmissions" ] ; then rm -r "bench/demosubmissions" fi mkdir "bench/demosubmissions" for f in "$1"/*.json; do name=$(basename "$f" .json) echo "Converting $f" racket infra/convert-demo.rkt "$f" "bench/demosubmissions/$name-unsorted.fpcore" racket infra/sort-fpcore.rkt "bench/demosubmissions/$name-unsorted.fpcore" "bench/demosubmissions/$name.fpcore" rm "bench/demosubmissions/$name-unsorted.fpcore" done ================================================ FILE: infra/convert-demo.rkt ================================================ #lang racket (require json) (require "../src/core/programs.rkt") (define op-map (make-hash `((expt . pow) (sqr . exp2) (abs . fabs)))) (define version-10-constants (make-hash `((e . E) (pi . PI)))) (define (format-op expr) (if (equal? (first expr) 'cube) (cons 'pow (append (rest expr) (list 3))) (cons (hash-ref op-map (first expr) (first expr)) (rest expr)))) (define (format-expr is-version-10 expr) (match expr [(? string?) (or (string->number expr) (raise (error "string that is not a num")))] [(list op args ...) (format-op (cons op (map (curry format-expr is-version-10) args)))] [(? symbol?) (if is-version-10 (hash-ref version-10-constants expr expr) expr)] [else expr])) (define (read-expr expr-string is-version-10) (format-expr is-version-10 (call-with-input-string expr-string read))) (define (make-fpcore expr) (define vars (free-variables expr)) (format "(FPCore (~a) ~a)" (string-join (map symbol->string vars) " ") expr)) (define (convert-file file-name output-file existing-set is-version-10) (define file-port (open-input-file file-name)) (define tests (hash-ref (read-json file-port) 'tests)) (define exprs-unfiltered (for/list ([test tests]) (read-expr (hash-ref test 'input) is-version-10))) (define exprs (for/set ([expr exprs-unfiltered] #:when (not (set-member? existing-set expr))) expr)) (for ([expr (in-set exprs)]) (fprintf output-file "~a\n" (make-fpcore expr))) exprs) (define (convert-files json-files output-files expr-set is-version-10) (unless (empty? json-files) (define json-file (first json-files)) (define sub-path (path-replace-extension (file-name-from-path (string->path json-file)) ".fpcore")) (define output-file (open-output-file (first output-files) #:exists 'replace)) (fprintf (current-output-port) "Creating file ~a\n" (path->string sub-path)) (define new-expr-set (set-union expr-set (convert-file json-file output-file expr-set is-version-10))) (convert-files (rest json-files) (rest output-files) new-expr-set #f))) (module+ main (define rebuilding? #f) (command-line #:program "convert-demo" #:args (input-json output-json) (convert-files (list input-json) (list output-json) (set) #t))) ================================================ FILE: infra/convert-json.rkt ================================================ #lang racket (require json) (require "../src/core/programs.rkt") (define (fix-expr expr pre-fpcore?) (let loop ([expr expr]) (match* (pre-fpcore? expr) [(_ (? string?)) (or (string->number expr) (raise-arguments-error 'fix-expr "string that is not a num" "expr" expr))] [(#t (list 'expt (app loop a) (app loop b))) (list 'pow a b)] [(#t (list 'cube (app loop a))) (list 'pow a 3)] [(#t (list 'cot (app loop a))) (list '/ 1 (list 'tan a))] [(#t (list 'sqr (app loop a))) (list '* a a)] [(#t (list 'abs (app loop a))) (list 'fabs a)] [(#t (list 'mod (app loop a))) (list 'fmod a)] [(#t 'e) 'E] [(#t 'pi) 'PI] [(_ (list op (app loop args) ...)) (list* op args)] [(_ _) expr]))) (define (make-fpcore test pre-fpcore?) (define expr (fix-expr (call-with-input-string (hash-ref test 'input) read) pre-fpcore?)) (define vars (map string->symbol (hash-ref test 'vars (λ () (map ~a (free-variables expr)))))) (define spec (fix-expr (call-with-input-string (hash-ref test 'spec (~s expr)) read) pre-fpcore?)) (define pre (fix-expr (call-with-input-string (hash-ref test 'pre "TRUE") read) pre-fpcore?)) `(FPCore ,vars ,@(if (hash-has-key? test 'name) (list ':name (hash-ref test 'name)) '()) ,@(if (not (equal? pre "TRUE")) (list ':pre pre) '()) ,@(if (not (equal? spec expr)) (list ':spec spec) '()) ,@(if (hash-has-key? test 'prec) (list ':precision (string->symbol (hash-ref test 'prec))) '()) ,expr)) (define (convert-files json-files pre-fpcore?) (define seen (mutable-set)) (for ([json-file (in-list json-files)]) (define data (call-with-input-file json-file read-json)) (for ([test (hash-ref data 'tests)]) (define expr (make-fpcore test pre-fpcore?)) (unless (set-member? seen expr) (set-add! seen expr) (pretty-print expr (current-output-port) 1) (newline))))) (module+ main (define pre-fpcore? #f) (command-line #:program "convert-json" #:once-each [("--pre-fpcore") "The demo file dates from before Herbie 1.0" (set! pre-fpcore? #t)] #:args json-files (convert-files json-files pre-fpcore?))) ================================================ FILE: infra/coverage.rkt ================================================ #lang racket (require cover json racket/cmdline racket/file racket/format racket/list racket/path racket/runtime-path racket/set) (define-runtime-path here ".") (define repo-root (simplify-path (build-path here ".."))) (define main-file (simplify-path (build-path repo-root "src" "main.rkt"))) (define src-dir (simplify-path (build-path repo-root "src"))) (define default-seed "1") (define default-benchmark "bench/hamming") ;; The full core RackUnit suite does not currently complete under `cover`; ;; `bsearch` and `regimes` currently trigger `cover` internal errors. (define skipped-rackunit-coverage-files '("src/core/bsearch.rkt" "src/core/regimes.rkt")) ;; These core test modules were probed individually and completed cleanly ;; in the merged tutorial run, so include them by default. (define stable-rackunit-coverage-files '("src/core/arrays.rkt" "src/core/batch-reduce.rkt" "src/core/egg-herbie.rkt" "src/core/egglog-herbie-tests.rkt" "src/core/prove-rules.rkt" "src/core/test-rules.rkt")) (define cover-namespace (begin (dynamic-require 'cover/cover #f) (module->namespace 'cover/cover))) (define (cover-internal name) (parameterize ([current-namespace cover-namespace]) (namespace-variable-value name))) (define current-live-files (cover-internal 'current-live-files)) (define with-cover-loggersf (cover-internal 'with-cover-loggersf)) (define make-cover-load/use-compiled (cover-internal 'make-cover-load/use-compiled)) (define compile-file (cover-internal 'compile-file)) (define run-file! (cover-internal 'run-file!)) (define get-namespace/internal (cover-internal 'get-namespace)) (struct file-summary (path relevant-lines covered-lines relevant-chars covered-chars) #:transparent) (define (collect-source-files) (sort (find-files (lambda (path) (and (equal? (filename-extension path) #"rkt") (not (regexp-match? #rx"/platforms/" (path->string path))))) src-dir) pathstring path)) ;; The syntax/utils tests add useful coverage quickly and ;; reliably. Add the core modules that we have confirmed ;; behave well under merged coverage too. (or (regexp-match? #rx"/src/syntax/" (path->string path)) (regexp-match? #rx"/src/utils/" (path->string path)) (member (path->string (find-relative-path repo-root path)) stable-rackunit-coverage-files)) (not (member (path->string (find-relative-path repo-root path)) skipped-rackunit-coverage-files)))) files)) (define (normalize-rackunit-additions rackunit-additions) (for/list ([path-string (in-list rackunit-additions)]) (simple-form-path path-string))) (define (path->coverage-key path) (path->string (simplify-path path))) (define (benchmark-label benchmark) (define path (simple-form-path benchmark)) (define name (path->string (file-name-from-path path))) (if (equal? (filename-extension path) #"fpcore") (path->string (path-replace-extension (file-name-from-path path) #"")) name)) (define (default-output-dir benchmark output-root) (build-path output-root (format "~a-run" (benchmark-label benchmark)))) (define (default-html-dir benchmark output-root) (build-path output-root (format "~a-html" (benchmark-label benchmark)))) (define (build-default-herbie-args benchmark output-root seed) (list "report" "--seed" seed benchmark (path->string (default-output-dir benchmark output-root)))) (define (with-coverage-runtime env source-files thunk) (parameterize ([current-cover-environment env]) (define cover-load/use-compiled (make-cover-load/use-compiled (map path->coverage-key source-files))) (parameterize ([current-load/use-compiled cover-load/use-compiled] [current-namespace (get-namespace/internal)] [current-live-files #f]) (with-cover-loggersf thunk)))) (define (run-benchmark-coverage source-files run-args) (define env (make-cover-environment)) (with-coverage-runtime env source-files (lambda () (compile-file (path->coverage-key main-file)) (run-file! (path->coverage-key main-file) 'main run-args))) (get-test-coverage env)) (define (run-rackunit-coverages source-files test-files) (printf "Running RackUnit tests...\n") (flush-output) (for/fold ([coverages '()] #:result (reverse coverages)) ([file (in-list test-files)]) (printf " RackUnit: ~a\n" (find-relative-path repo-root file)) (flush-output) (with-handlers ([exn:fail? (lambda (e) (printf "Skipping RackUnit coverage for ~a\n" file) (printf " ~a\n" (exn-message e)) (flush-output) coverages)]) (define env (make-cover-environment)) (with-coverage-runtime env source-files (lambda () (compile-file (path->coverage-key file)) (run-file! (path->coverage-key file) 'test #()))) (cons (get-test-coverage env) coverages)))) (define (covered-char-status coverage path pos) (with-handlers ([exn:fail? (lambda (_) 'irrelevant)]) (coverage (path->coverage-key path) pos))) (define (merged-char-status coverages path pos) (define statuses (for/list ([coverage (in-list coverages)]) (covered-char-status coverage path pos))) (cond [(member 'covered statuses) 'covered] [(member 'uncovered statuses) 'uncovered] [else 'irrelevant])) (define (segment->jsexpr path segment) (match-define (list status start end start-line start-col end-line end-col text) segment) (hasheq 'file (~a (find-relative-path repo-root path)) 'status (~a status) 'start start 'end end 'start-line start-line 'start-column start-col 'end-line end-line 'end-column end-col 'text text)) (define (collect-file-segments coverages path) (define text (file->string path)) (define len (string-length text)) (let loop ([chars (string->list text)] [pos 1] [line 1] [col 1] [active #f] [segments '()]) (define (finish active segments end-pos end-line end-col) (match active [#f segments] [(list status start-pos start-line start-col chars) (cons (list status start-pos end-pos start-line start-col end-line end-col (list->string (reverse chars))) segments)])) (match chars ['() (reverse (finish active segments len line col))] [(cons ch rest) (define status (merged-char-status coverages path pos)) (define next-line (if (char=? ch #\newline) (+ line 1) line)) (define next-col (if (char=? ch #\newline) 1 (+ col 1))) (cond [(eq? status 'irrelevant) (loop rest (+ pos 1) next-line next-col #f (finish active segments pos line col))] [active (match-define (list active-status start-pos start-line start-col active-chars) active) (if (eq? status active-status) (loop rest (+ pos 1) next-line next-col (list active-status start-pos start-line start-col (cons ch active-chars)) segments) (loop rest (+ pos 1) next-line next-col (list status pos line col (list ch)) (finish active segments pos line col)))] [else (loop rest (+ pos 1) next-line next-col (list status pos line col (list ch)) segments)])]))) (define (summarize-file coverages path) (define text (file->string path)) (define relevant-lines (mutable-set)) (define covered-lines (mutable-set)) (define relevant-char-count 0) (define covered-char-count 0) (let loop ([chars (string->list text)] [pos 1] [line 1]) (match chars ['() (file-summary path (set-count relevant-lines) (set-count covered-lines) relevant-char-count covered-char-count)] [(cons ch rest) (define status (merged-char-status coverages path pos)) (unless (eq? status 'irrelevant) (set-add! relevant-lines line) (set! relevant-char-count (+ relevant-char-count 1)) (when (eq? status 'covered) (set-add! covered-lines line) (set! covered-char-count (+ covered-char-count 1)))) (loop rest (+ pos 1) (if (char=? ch #\newline) (+ line 1) line))]))) (define (summaries->totals summaries) (for/fold ([relevant-lines 0] [covered-lines 0] [relevant-chars 0] [covered-chars 0]) ([summary (in-list summaries)]) (values (+ relevant-lines (file-summary-relevant-lines summary)) (+ covered-lines (file-summary-covered-lines summary)) (+ relevant-chars (file-summary-relevant-chars summary)) (+ covered-chars (file-summary-covered-chars summary))))) (define (percent covered relevant) (if (zero? relevant) 0.0 (* 100.0 (/ covered relevant)))) (define (display-summary label summaries) (define relevant-summaries (filter (lambda (summary) (positive? (file-summary-relevant-lines summary))) summaries)) (define-values (relevant-lines covered-lines relevant-chars covered-chars) (summaries->totals relevant-summaries)) (printf "~a\n" label) (printf " files with relevant coverage: ~a\n" (length relevant-summaries)) (printf " line coverage: ~a / ~a (~a%)\n" covered-lines relevant-lines (~r (percent covered-lines relevant-lines) #:precision '(= 2))) (printf " char coverage: ~a / ~a (~a%)\n" covered-chars relevant-chars (~r (percent covered-chars relevant-chars) #:precision '(= 2))) (printf " most uncovered files:\n") (for ([summary (in-list (take (sort relevant-summaries > #:key (lambda (s) (- (file-summary-relevant-lines s) (file-summary-covered-lines s)))) (min 10 (length relevant-summaries))))]) (printf " ~a: ~a / ~a lines (~a%)\n" (find-relative-path repo-root (file-summary-path summary)) (file-summary-covered-lines summary) (file-summary-relevant-lines summary) (~r (percent (file-summary-covered-lines summary) (file-summary-relevant-lines summary)) #:precision '(= 2))))) (define (detailed-report-path label output-root run-rackunit?) (build-path output-root (format "~a-~a-coverage.json" label (if run-rackunit? "merged" "benchmark")))) (define (write-detailed-report label output-root run-rackunit? summaries coverages) (make-directory* output-root) (define relevant-summaries (filter (lambda (summary) (positive? (file-summary-relevant-lines summary))) summaries)) (define-values (relevant-lines covered-lines relevant-chars covered-chars) (summaries->totals relevant-summaries)) (define out-path (detailed-report-path label output-root run-rackunit?)) (call-with-output-file out-path (lambda (out) (write-json (hasheq 'label label 'kind (if run-rackunit? "merged" "benchmark") 'line-coverage (hasheq 'covered covered-lines 'relevant relevant-lines 'percent (percent covered-lines relevant-lines)) 'char-coverage (hasheq 'covered covered-chars 'relevant relevant-chars 'percent (percent covered-chars relevant-chars)) 'files (for/list ([summary (in-list relevant-summaries)]) (define path (file-summary-path summary)) (hasheq 'file (~a (find-relative-path repo-root path)) 'line-coverage (hasheq 'covered (file-summary-covered-lines summary) 'relevant (file-summary-relevant-lines summary) 'percent (percent (file-summary-covered-lines summary) (file-summary-relevant-lines summary))) 'char-coverage (hasheq 'covered (file-summary-covered-chars summary) 'relevant (file-summary-relevant-chars summary) 'percent (percent (file-summary-covered-chars summary) (file-summary-relevant-chars summary))) 'segments (map (curry segment->jsexpr path) (collect-file-segments coverages path))))) out)) #:exists 'replace) out-path) (module+ main (define html-output-dir #f) (define output-root (build-path repo-root "tmp-coverage")) (define run-label #f) (define run-rackunit? #f) (define rackunit-additions '()) (define seed default-seed) (define benchmark default-benchmark) (command-line #:program "coverage" #:once-each [("--benchmark") benchmark-path "Benchmark file or directory for the default Herbie run" (set! benchmark benchmark-path)] [("--rackunit") "Run RackUnit test submodules before the benchmark run" (set! run-rackunit? #t)] [("--seed") seed-value "Seed for the default Herbie run" (set! seed seed-value)] [("--output-root") dir "Directory for coverage HTML and report output" (set! output-root dir)] [("--html") dir "Directory for HTML coverage output" (set! html-output-dir dir)] [("--label") label "Label to print for this run" (set! run-label label)] #:multi [("--rackunit-add") path-string "Add a RackUnit file to the default stable subset" (set! rackunit-additions (cons path-string rackunit-additions))] #:args herbie-args (define effective-run-label (or run-label (benchmark-label benchmark))) (define effective-html-dir (or html-output-dir (default-html-dir benchmark output-root))) (define effective-herbie-args (if (null? herbie-args) (build-default-herbie-args benchmark output-root seed) herbie-args)) (make-directory* output-root) (define source-files (collect-source-files)) (define default-test-files (collect-rackunit-files source-files)) (define test-files (remove-duplicates (append default-test-files (normalize-rackunit-additions rackunit-additions)))) (define run-args (vector->immutable-vector (list->vector effective-herbie-args))) (define benchmark-coverage (run-benchmark-coverage source-files run-args)) (define coverages (if run-rackunit? (cons benchmark-coverage (run-rackunit-coverages source-files test-files)) (list benchmark-coverage))) (define summaries (map (curry summarize-file coverages) source-files)) (define relevant-files (for/list ([summary (in-list summaries)] #:when (positive? (file-summary-relevant-chars summary))) (path->coverage-key (file-summary-path summary)))) (when effective-html-dir (if run-rackunit? (printf "Skipping HTML output for merged benchmark+RackUnit coverage.\n") (begin (make-directory* effective-html-dir) (generate-html-coverage benchmark-coverage relevant-files effective-html-dir)))) (define report-path (write-detailed-report effective-run-label output-root run-rackunit? summaries coverages)) (printf "Detailed report: ~a\n" report-path) (display-summary effective-run-label summaries))) ================================================ FILE: infra/diagrams/.gitignore ================================================ *.png ================================================ FILE: infra/diagrams/1.5/herbie-system.drawio ================================================ 7V1Zk5u4Gv01XZV5MMUisTymlyz3JreS9OROkpcpbMtuJhg8gNP2/PqRQAJt3gHTXd2VqhgBAh+db9Un+cq5WazfZuHy4WM6RfGVbU7XV87tlW1bwDbxf6RlU7V4ll01zLNoSi9qGu6jfxBtpPfNV9EU5cKFRZrGRbQUGydpkqBJIbSFWZY+ipfN0lh86jKcI6XhfhLGausf0bR4oK2WaTYn3qFo/kAf7UN6YhGyi2lD/hBO00euybm7cm6yNC2qT4v1DYoJeAyX6r43W87WL5ahpDjkhm/5z+jH7fJv387/caP1w+T9/1+PHLfq5lcYr+g3pm9bbBgEWbpKpoj0Yl05148PUYHul+GEnH3Eg47bHopFTE/nRZb+RDdpnGbl3Y4VmOYb/E7XsyiOufY35R9uV78H/Wq/UFagNddEv9dblC5QkW3wJfSsHVCMGclMWB0/NkPm+PSaB2606mEMKU3mdd8NkvgDBVMPrDv69uPd15l3E73P8+9m9vP7fTaCoGVgZ2lSUPGwgA5o07wB5ZkWAPU9AwqIWoGKaGBrEAVuC4jqqWppEHXjgmCxDBMBWvfvFRGr60kFz2t8MpuPw1f43fA//HxT++k38pEAaBK0R7NwEcWb6vZFmqR5OTjCJXk5IuQCc7lunos/zav/oawbIf7KpLUU/PqIQQBLEHDLLflMXgySrwkxcvuutepr2dif1I3ddFMhXZ9ZplFS5M3pCu76NEqmr4nCLVsmcZjn0YR8rkhMGq3ykLGetJhlC1pHxbfq2PBg3fRduuR2LTdsuIbmvdB0zr3WDljqUzUijSRARvX6atigyBivPZehOCyiX/pXCPOmec49THiLTwTo8vJ1czVwA8MDTU8b7hT/eP4JebrKJqjuTmWA/kmeadj6JznelicVYTZHxdYnlYcCvOIJykS+UZQP1oEsSJX8Y31SqQAmda1asCC4uWnLUlmWqFcdjV7VWiobdGWpAqdDS2XrAL3DcAKzHUAlyw80ZopB3o/hB37XHlWbfITmAXwEGgC746Nq5u+jxTKOZpsdOJr7cWwDLUdiGwBG4KqM0/lFnfENKnhl6DHDUOQKXsQy3tPDNCse0nmahPFd03otItpc8yFNlxTHv1BRbKh8h6siFVGubDm15MyOV71RG84dbNhBgpGo7vJdjzWU9xlBELCG5ubySLj7E8oiDCjKaOPW0a5s4g5AqbiWXsQuTtR2Xgg5NcNLb6XGkXHJMy1DFj4X0wkGzZ/Yp2BjJdbU73U6kWyFR1/QZJXlpStT2dlxxkzsl4piF5dI17GlSAXoTECfAqma09/DDVHeCoh3a+y0TC+OoecMDEE1dP6C5li6VQTfJzOEv/nk8kT0PQVEwwSXxVHN7XwMoyQmqvzScFm2pPpslXOur/E7OgNLddvu1kUWToooTS4Ol8wuHVy9UotlcTm4PsWreUTSMO8TbIZn4QCEUvZubUuFzXJ6xU2XFqy0WsSU2jLNo4ILbaMmrnXDBUEpGefkvyVF/MIoA19E2dOAbPYKsuoSKyDV2SrnNkkT4vpOw/yhjsk4hEj7p7DAnE7KFtt06qiMTQnYiuu8Fde9fiUHG9SgxtrOdD9hIA4aO2Q9CKkjm5tVkPpxnd39dO20espQhzF5vSIcx6oC4kadZSiFsRbHkMUyLK6pAhIW1uwLZcQwBu4JYrbyZW+8Qq18BfR+AzcQ/vkS/4BzIgEDIJlG/zAGYh6EG+4ylt1un6OaiQu0Xmaa8PwkdpqGzUfa1oH0pHfVDO2Mn/6B/ASD4qcHWuKnF5zGz9bYp3pqk3QxjhIyBSPHU4SXrdHytASQJ9DS6oyW4EmqzWdDS6BXiueSr+GR5XvH6LfjkohDYQOEhuWJ4+ieSggIjCBQko9yTqgveqhaC5XZMqK1noN+GgiDfHNL7u9Y+sgdKTzsmjBq2vVFnxzLBhfahuuZ9Z/Vjmoh3e6hWcfsgLqMQOX8kNlqgSKshkeq77F8XYHPO5SNI8Qla6ruttQhFGhd7JrcpZkIvlKONoVxNCfphwlKSjpdk/RMNAnj1/TEIppOyxk8Xb5HJLVcSVYe05e0t1L0mBoHcZbU1uV2NSR2OsuDH1DhcJxekEscGqNyuE3ZivShEdVQlIbsSZqnRvK2yJueU0mumppNV8VyRd6QmJJX+W8qa/AQfAjHKBbJcri0ZghrmDJVVZGCZiFw5/D6Ct5qqKbSZjfnZbGtS6XpU6/4amSdOJsGsFzRyWQ1GmcyZyTaF1+8P53NctTNSKvWYOsAv7gL28cPQvw9d7gLTXHMsaoAQlEV8IU2fakDdUIwJ3VI6JkkSAbCIdfcP9IH+5lSXzoGdswaz1RYc0c+hwOolbGgaKihq/pl9bqbXibqPDXKz9DfK5QXamqylL0F/vahdkb+TP+NU9zQFeTLMJ19MqZV3pxL6AtybpjA3i3r+KC9crZDp4fYjP5A9AJUCkQkEh5sSoBoluyeQ0+vx+DDr48F6vbEtIExSLYGysAfbFYsybGxe2aQJjBZoiwsUjKHeKOqymwVoy1n0BJHHEyJ6guEhxnPeK3FMyPTwDGrSI3zCMfiVfGG7uIXTy166EylnOSi9jSFNzCFAyX/yj41EQKlmpp60VD7CueP2Y/5/eR9YK7fWh/tD9efN5OvI6bvOH5hpyxT11/0oC4OVQ9MJFpRD740AKym++x8h2iRRr1lPHxVY3zKVsnlYxLbk8O//gpbtauM1dTQhxQzl+jGS2Mlx28Xx0ol1X1IorSLI+X4+5HqrCRVC5WaTGq/TPGohNLpVpfpkoFYXUdKFcJTK1kdxxc66nAGW0uRQEeRs4sEW6/3218wNSx+2Gx987mz2ECKJ6FcetUxPzRLLs4kx2Fe/+4UwlaeDGX4pbqU4MQ0ktxPh0kA/eiri7mipJmc1Eb7ONbHzu800idML+G77yZ2K757YLXjrOsjgFZddT0aWjswODMwEPG2PCiVFp2s3y11BWevEs4S3NzAxzj02JKou6CHOJChl0X05NJF3NE+DnU99KppR3h0s5dh3z/s8FR5V4cddFcDrx92TR3Ky7Bvic4sYLQ08I514WFXXblJmp9t4p/lqAOzpTEH5qWFXU0BvRSoH638/WBHgboypAebAtztHuXSNTtUrz9fjdsliOMHPEFGpBLlOZJEceRPpQUJLhx325Io6DoGMBUu9kQYzbLOo1c0AN2Kht+11VdPcIVDmysa5FBDt79wRysa9KOvqwDgR18ctfPWuHyIxlmYRaSkZAsJXtiya07TY9O+/OwT0NBFTim2RhfN4rjBbZ1MT43Dyc95OT4j8Q1e2WRnTfo4GwTNZ0ge/qx3XkbVTlTlpsn0Cnnz5Tqbq9lxuRKWclvmSlxIoygw5CQVmXKP5jq322ywXG//XD4SEoThbXmmnkcoH2lL2zFTwdGhxad3NZApaNa3OtoNm7mHwKD09zVbGLsediG5P++wzZk1uxl3vGlxz7uBeI4QPRimWRdG97PvpmZ+8cf8f4/Jn7++/7WJ3iSfv3z87+v/rEbDcjZxoGCIi6OADwzAOYZsgf65a2C87uYbV2/M1S/z82Ix+/PHne2i2edZrNk9UB+ODGYeCbS45M1id7a7xA1KQ9jdNBJQi4bP1DD8bLEvKo/jliRxmaqqo2ZlRLm1Wgf16rsUyf6VEcNSOAAYLtiWAvGc9pZPQa8zhaMdD1XhVAvudKnR4SgdJmdtTF5DCMQapHMX2vagZw7Y7v5lrrpyDCw5B316LZJUSdzhXLW+Qly1Lk+9CqUmchuCbPrWmZLb4Zp4/ddXE9Dcjogv01Ki+MHqt3h2SeDBouxWv7azSy10nEtmqBwkzfVLDEnD8z6c/ivawyKQYgpOnqQApmtI6ekOI8Wd4PIb9kf4QKHJpdcB1MBcbB0AVDOxVbZTlbQiW5GfHxuQ9uUjqichZaqaPrmAQKOmO6wg0Dtd27P4503wfM1JTvjJTdn0uIUZ265sD2/bmO/blargDfR8fnH1Ku/R0Kt61eKkC0bbmBR/ixKy4F7zCzRPUG5alBMvkOVEFZSuJsb1gqK6ssrva4yjJMw2ZLs33U9snCNTwk9ItACvLf3eXJ0/vpiAaZaGb8HXsYePr+P2h+8VnR3kXIJmXtC5+xc= ================================================ FILE: infra/diagrams/1.6/herbie-system.drawio ================================================ 7V1bj5u6Fv41I3U/TGQuhvDYufQitVLbac9u+3LEJE6GUwIpkE6yf/3GYAO2FwlDgDBzMqrUYMDAWp/X3faFcb3avo3c9cPHcE78Cx3NtxfGzYWuaxiZ6X+0ZZe32Jg1LCNvzi4qG+68fwhrRKx1481JLFyYhKGfeGuxcRYGAZklQpsbReGjeNki9MWnrt0lURruZq6vtv7tzZMH1qohVJ54R7zlA3v0FLMTK5dfzBriB3cePlaajNsL4zoKwyT/tdpeE58Sj9Mlv+9NzdnixSISJE1u+B7/8n7erH9P9fgfy9s+zN7/5/WlYeXd/HH9Dfti9rbJjpMgCjfBnNBetAvj6vHBS8jd2p3Rs48p09O2h2Tls9NxEoW/yHXoh1F2t6E5CL1J3+lq4fl+pf1N9pe2q9/BPu0PiRKyrTSx73pLwhVJol16CTurO4zGHGQI58ePJctMm13zUOFWwUaXwWRZ9F1SMv3BiAkT1rr8/vPdt4V97b2P4x8o+vXjLrrkMO+MsIswSNjw0EyI0Ahdm9mZDggq0VNzVHo6OkBP0+qAnjBQNYCelp9QSqzdQCCs9XtDB9XVLCfO6/RktLx3X6Xvlv5Ln4/AX3/Rn5R8iNL6cuGuPH+X374KgzDOWCNcEmf8oBeg9bZ8bvprmf+PZcmI00+mrdmwL444CXBGhLTlhv6mL4bpZ+KUcoeu1YprOedbdaOX3eSULs6sQy9I4vJ0Tu7iNAnmr6m4zVpmvhvH3oz+ziFMG7XskGOetqCshWy95Ht+PLFx0fRDuuRmKzfsKg3le5H5svJae8hSnCooUo4DzKFeXI1LKnLEg+ci4ruJ9wd+BTcum5eVhwlv8YkSOrt8W15tWs7ENsuedpVT1cdXnxCHm2hGiu5UBMBPstFEh59k2DVPStxoSZLaJ2WHAnnFEwyJ1UZxfPAO5IGUj/9UnuQigI+6TvWX41xfd6WnNA0LctUA5KoxBeSqbvalpxyjRz2lQwS9Tclpol70lAmoKU7yYdS+Oe3bnuoSjxg1wCMGCNgfHlU1f+et1r632O2hIzpMxy6oJY1ebAFwg4yi3sCGFWJF5DFK6RArxKJq8Y4dhlHyEC7DwPVvy9YrkZzlNR/CcM2I+D+SJDs2uN1NEookzhU5U+Nciee9MQVeOdixg1q+5Nprz9ezgZXp+33cKzSy4BoCvGC3MjXGuW4jXZQxUyx2ISg/iaPFa7Rnsq7w+AuZbaI4szFyBXgfcd33JWf/yYeKZYxsqKha7qu7ozJVIeHtNrUl5ienoI1HRkHVn/1Clt4KAOH7YEHSL5+dHoZTWySiCRkIQxJRFdif3GSWuhnoq3vvn55gsoqDCGYYQ1JMjU99dL3Apzrp1MTSdMl60lViWXhIYqnG5+02idxZ4oXBycklD0aIXIMORh5iBkJKHpdl917gRjvLrPiaXuloWu6K0im4j+l/a3+z9E5PZ8MS6WyrZC78n2HorJownxip0PsgIdHCHYGukH0hXQPoNqjo0yDXHMSnoT8jfBYG9GjwCYXqJTqvw9hLnhGRZQP89ERWTR+FSEUM2bgJwoD6pHM3figiJRUK0fbUckplR5C16MgoYiU8TacrPm0tXQ/6kBWyQQqdtx3pamJHZJojeZpCQLfkh9KPzHy5n749VpXV8eaebNfRPo7znIHAZ5F/KduiXR5hMKYOb8hiDBOk8eMyzpAd7apHn0iUuiwpbA4hIifRYUt+JMixTEPgeGEDPBU6WK9xAIbCjq1gx/WTjCGQf/RE/LAAFQ9W5cjhsSo4PlViTsQbPgC3WmQdjGtZDRE4HRUCp5LsMo2WCHRMyU1oGG9LceDuKpfxfGVtXE92R9hzS0DnPXYKbyCLTeUiEK5tBWw00auRV60hstldBbh7g/a0IbTNUUHbNjuCtu20g3Zn6FMdsVm4Sl0Imo+Xo3hd6Os2CYEKLG0BlocUfHtYms9S4r4YWJqwUDwWfCWOtKn9FPn2NEtxLGiw8USzRUDIfGwMCNucONU/oVss25Z9w0OVWiTL0VCp9RLk00gQNJVMf4XPTeEjdzR0xtRU42ZnefJUNFiYyhNU/GndiBarLq05EDawGpvgpg8tXBIAwss5pVJPbQrVer4j0b1HKhHCvLuakrSEbJOLPXU+LPxVLZlmTa7vLWnMa0aCDExXNCbozVz/NTux8ubzrJ4DCjKKkJZLirNj9pJ6LUCfUu4mlmfpUIIMgLDRVxDSalDs9jSpIFe7lSpliBKYcRmklmxHorYhAF3EzcDxS0vNB4SbZL2hb0gVyav4LxU1KQs+uPfEF8HSfLRGJJUwWYwrBwULX6Sd46sLfANATYXNfszLw7aYM8OeelGdlgINZzQxNUs0MXm53pHIuRS1y1S8P1wsYtIPp1VtUMvgs7FQzz9s9GIs4LoqsqEEgVpPEdNiVPJCAiNjQc+0I8dD7mhox4PPMKvW39Df7ggqMrUG9YTFpMtBMsK26tdH5PeGxIkajMxG3Sr9ehcsZTrSZqsIa2wJI2uCjFZ5xXKAW5olZJvQgaB8etBY+neWSuIVOmMRCEphnYTBxgLBFDWRPnA20x7Q35gWxwJyB0LayBBkIbSf8Y3jFZqIoKJocSgEAb7ImkRuEtKk4bUqKaONT2rOkHXqZHAZCs8QGacLY3fmwlyiSeqmitA4DnDcRRVv6M9lsaEAVl/q8Elm5rChDy5wDus2NCrJJOs2vW2QBEv1C4WBN5RkUit1etNtrZykgZLHzYFojAuIuCsgSl661p+K/Hvxc3k3e++g7Vvto/7h6vNu9u2Sa+gKEFMvIlLniw6g4JoqND52OlFoU4kBfLJbW4AMoMQazJI+KzGgRH0kssOSAvStZYdsp/coO+AEiqrEPkWb4PThGt0WCTPkzEVw/Z365Xca501NKG/6lgTUnQGmkT7DXGqHuVNZq5rAklbQbKQucqcgAFRHdE2nrkJLDbSMhVuGcbpQ2bjq+W3dFLkvs7VxKZfUkTIxoDsBC6JG9VY/hOkAo0Lh1CJWjoifXMSquujOpXHvk1PKkPMqAKV6m00Gkkq1H7ufJTLQMibcBBmJ5DGwaHjgtpPQDGN6UsnjQBA5Wll1PmficNH5uPChOx3NNDMl0x/L5es94wOYlX4kOJrFr/YbNbU4GQv7pVnyTku7RO6nx7wKzH11XREvKEu8wATKOiKzMJh7cAr6FMGl/cDuJLjkaEdGk7g/I7Cbh6g6DTXB1AD1wOjUwEiGt2bjiTQw28p3rW7tpYFGOA9RVRjvp65HTe7zhBbiSFgvD1GFX41Fu34QQ32zXlXtJOVudGb7Yba3Lr5T2d5j+R3MdqCa98z2Gu9MMycdMd6Q14wbmu2qKTcL46NV/Ivkuok64rmJTj3Y1RDQeZLfk4X/1JlY3dft024Hq9yH0aFa/X0vQZQ6S8h8iSBRDPm2sKDOhWHVTys3JiZSsDgQYIClMbrJb34F69n/v3Ob8sKpJ89tGlAtW5X7IteOmyn8wbuP3MijidMaEJzRsq8UwuYVndXskwnARQ4pdpcKBxYsWS4Vdpx68WbbUIeVDm2H0VuaDliJYXSbNrFT9+7s1zLj2KX4Bq90uqcHe5xuOuVvTB/+ovd8Ivnq0dl2TewKedunIuwN7PWUS5VsQ6hcrtBGUbLQk0y2ZLtDFUHwcmunYuOp7JGYUhjfZGeKhEv2SF3aCIoNHIha1Tg4QDKFmsWtBSHL0SfunoSdzDECNk+y7NTWrvzZzbaFAvZR6nm7pKcZxGO3XrFh7nFxrK6Knu3+spebN2jzB31erRb//XmrW2TxeeEDy+LDzs1oslJmh8sQaPzOUVQ4w5+rlssdOdCq2eWp6Ik+bTp4GdkyhCmD1IHtq+Rurxl1uPp5XBJlKlZA2LL51Lb4uZjRM5AMAdY9z5c3gIKn4xEk/K27SG+nnqg+elHS+WSJF5u+NjU5LN2+PElau6rH9DU8q0kdnM+9MKUAchcjF021I40Axtg+FhuCP1+NSZdrVJ8zVdLww/l+t/tGYOOhbOU72u4TC32XwavFKPWjuXiJMUn4qpEGf6I+LgApqqB13sJE1kQfzN3bS9zq7nteeqDA5NRTA6CY47BTA7Aac8zjeupIS6IN3eJ7RNL3oM80tlGmiunWNQWAmO6xqAA2uurj1cflfL7FNPr57LI4A64Ny9eBPYDbLlKAsDNcvzDwOVVxTlWcUxX6OVXRszK1sDVBjiQVW6+zbk34xgknSlEAU2zGk6PYrwNeXI4C/txeV3B6FjmKkYx8GxkTs3bDFdswJ47VTg7Qnh1RDigypW85oMav/7iR54KRr/HIgi6XaDptmuGCKd3K5aW6NW7/BQ== ================================================ FILE: infra/diagrams/1.6-changes/herbie-system.drawio ================================================ 7V1bj5u6Fv41I3U/TGQuhvDYufQitVLbac9u+3LEJE6GUwIpkE6yf/3GYAO2FwlDgDBzMqrUYMDAWp/X3faFcb3avo3c9cPHcE78Cx3NtxfGzYWuaxiZ6X+0ZZe32Jg1LCNvzi4qG+68fwhrRKx1481JLFyYhKGfeGuxcRYGAZklQpsbReGjeNki9MWnrt0lURruZq6vtv7tzZMH1qohVJ54R7zlA3v0FLMTK5dfzBriB3cePlaajNsL4zoKwyT/tdpeE58Sj9Mlv+9NzdnixSISJE1u+B7/8n7erH9P9fgfy9s+zN7/5/WlYeXd/HH9Dfti9rbJjpMgCjfBnNBetAvj6vHBS8jd2p3Rs48p09O2h2Tls9NxEoW/yHXoh1F2t6E5CL1J3+lq4fl+pf1N9pe2q9/BPu0PiRKyrTSx73pLwhVJol16CTurO4zGHGQI58ePJctMm13zUOFWwUaXwWRZ9F1SMv3BiAkT1rr8/vPdt4V97b2P4x8o+vXjLrrkMO+MsIswSNjw0EyI0Ahdm9mZDggq0VNzVHo6OkBP0+qAnjBQNYCelp9QSqzdQCCs9XtDB9XVLCfO6/RktLx3X6Xvlv5Ln4/AX3/Rn5R8iNL6cuGuPH+X374KgzDOWCNcEmf8oBeg9bZ8bvprmf+PZcmI00+mrdmwL444CXBGhLTlhv6mL4bpZ+KUcoeu1YprOedbdaOX3eSULs6sQy9I4vJ0Tu7iNAnmr6m4zVpmvhvH3oz+ziFMG7XskGOetqCshWy95Ht+PLFx0fRDuuRmKzfsKg3le5H5svJae8hSnCooUo4DzKFeXI1LKnLEg+ci4ruJ9wd+BTcum5eVhwlv8YkSOrt8W15tWs7ENsuedpVT1cdXnxCHm2hGiu5UBMBPstFEh59k2DVPStxoSZLaJ2WHAnnFEwyJ1UZxfPAO5IGUj/9UnuQigI+6TvWX41xfd6WnNA0LctUA5KoxBeSqbvalpxyjRz2lQwS9Tclpol70lAmoKU7yYdS+Oe3bnuoSjxg1wCMGCNgfHlU1f+et1r632O2hIzpMxy6oJY1ebAFwg4yi3sCGFWJF5DFK6RArxKJq8Y4dhlHyEC7DwPVvy9YrkZzlNR/CcM2I+D+SJDs2uN1NEookzhU5U+Nciee9MQVeOdixg1q+5Nprz9ezgZXp+33cKzSy4BoCvGC3MjXGuW4jXZQxUyx2ISg/iaPFa7Rnsq7w+AuZbaI4szFyBXgfcd33JWf/yYeKZYxsqKha7qu7ozJVIeHtNrUl5ienoI1HRkHVn/1Clt4KAOH7YEHSL5+dHoZTWySiCRkIQxJRFdif3GSWuhnoq3vvn55gsoqDCGYYQ1JMjU99dL3Apzrp1MTSdMl60lViWXhIYqnG5+02idxZ4oXBycklD0aIXIMORh5iBkJKHpdl917gRjvLrPiaXuloWu6K0im4j+l/a3+z9E5PZ8MS6WyrZC78n2HorJownxip0PsgIdHCHYGukH0hXQPoNqjo0yDXHMSnoT8jfBYG9GjwCYXqJTqvw9hLnhGRZQP89ERWTR+FSEUM2bgJwoD6pHM3figiJRUK0fbUckplR5C16MgoYiU8TacrPm0tXQ/6kBWyQQqdtx3pamJHZJojeZpCQLfkh9KPzHy5n749VpXV8eaebNfRPo7znIHAZ5F/KduiXR5hMKYOb8hiDBOk8eMyzpAd7apHn0iUuiwpbA4hIifRYUt+JMixTEPgeGEDPBU6WK9xAIbCjq1gx/WTjCGQf/RE/LAAFQ9W5cjhsSo4PlViTsQbPgC3WmQdjGtZDRE4HRUCp5LsMo2WCHRMyU1oGG9LceDuKpfxfGVtXE92R9hzS0DnPXYKbyCLTeUiEK5tBWw00auRV60hstldBbh7g/a0IbTNUUHbNjuCtu20g3Zn6FMdsVm4Sl0Imo+Xo3hd6Os2CYEKLG0BlocUfHtYms9S4r4YWJqwUDwWfCWOtKn9FPn2NEtxLGiw8USzRUDIfGwMCNucONU/oVss25Z9w0OVWiTL0VCp9RLk00gQNJVMf4XPTeEjdzR0xtRU42ZnefJUNFiYyhNU/GndiBarLq05EDawGpvgpg8tXBIAwss5pVJPbQrVer4j0b1HKhHCvLuakrSEbJOLPXU+LPxVLZlmTa7vLWnMa0aCDExXNCbozVz/NTux8ubzrJ4DCjKKkJZLirNj9pJ6LUCfUu4mlmfpUIIMgLDRVxDSalDs9jSpIFe7lSpliBKYcRmklmxHorYhAF3EzcDxS0vNB4SbZL2hb0gVyav4LxU1KQs+uPfEF8HSfLRGJJUwWYwrBwULX6Sd46sLfANATYXNfszLw7aYM8OeelGdlgINZzQxNUs0MXm53pHIuRS1y1S8P1wsYtIPp1VtUMvgs7FQzz9s9GIs4LoqsqEEgVpPEdNiVPJCAiNjQc+0I8dD7mhox4PPMKvW39Df7ggqMrUG9YTFpMtBMsK26tdH5PeGxIkajMxG3Sr9ehcsZTrSZqsIa2wJI2uCjFZ5xXKAW5olZJvQgaB8etBY+neWSuIVOmMRCEphnYTBxgLBFDWRPnA20x7Q35gWxwJyB0LayBBkIbSf8Y3jFZqIoKJocSgEAb7ImkRuEtKk4bUqKaONT2rOkHXqZHAZCs8QGacLY3fmwlyiSeqmitA4DnDcRRVv6M9lsaEAVl/q8Elm5rChDy5wDus2NCrJJOs2vW2QBEv1C4WBN5RkUit1etNtrZykgZLHzYFojAuIuCsgSl661p+K/Hvxc3k3e++g7Vvto/7h6vNu9u2Sa+gKEFMvIlLniw6g4JoqND52OlFoU4kBfLJbW4AMoMQazJI+KzGgRH0kssOSAvStZYdsp/coO+AEiqrEPkWb4PThGt0WCTPkzEVw/Z365Xca501NKG/6lgTUnQGmkT7DXGqHuVNZq5rAklbQbKQucqcgAFRHdE2nrkJLDbSMhVuGcbpQ2bjq+W3dFLkvs7VxKZfUkTIxoDsBC6JG9VY/hOkAo0Lh1CJWjoifXMSquujOpXHvk1PKkPMqAKV6m00Gkkq1H7ufJTLQMibcBBmJ5DGwaHjgtpPQDGN6UsnjQBA5Wll1PmficNH5uPChOx3NNDMl0x/L5es94wOYlX4kOJrFr/YbNbU4GQv7pVnyTku7RO6nx7wKzH11XREvKEu8wATKOiKzMJh7cAr6FMGl/cDuJLjkaEdGk7g/I7Cbh6g6DTXB1AD1wOjUwEiGt2bjiTQw28p3rW7tpYFGOA9RVRjvp65HTe7zhBbiSFgvD1GFX41Fu34QQ32zXlXtJOVudGb7Yba3Lr5T2d5j+R3MdqCa98z2Gu9MMycdMd6Q14wbmu2qKTcL46NV/Ivkuok64rmJTj3Y1RDQeZLfk4X/1JlY3dft024Hq9yH0aFa/X0vQZQ6S8h8iSBRDPm2sKDOhWHVTys3JiZSsDgQYIClMbrJb34F69n/v3Ob8sKpJ89tGlAtW5X7IteOmyn8wbuP3MijidMaEJzRsq8UwuYVndXskwnARQ4pdpcKBxYsWS4Vdpx68WbbUIeVDm2H0VuaDliJYXSbNrFT9+7s1zLj2KX4Bq90uqcHe5xuOuVvTB/+ovd8Ivnq0dl2TewKedunIuwN7PWUS5VsQ6hcrtBGUbLQk0y2ZLtDFUHwcmunYuOp7JGYUhjfZGeKhEv2SF3aCIoNHIha1Tg4QDKFmsWtBSHL0SfunoSdzDECNk+y7NTWrvzZzbaFAvZR6nm7pKcZxGO3XrFh7nFxrK6Knu3+spebN2jzB31erRb//XmrW2TxeeEDy+LDzs1oslJmh8sQaPzOUVQ4w5+rlssdOdCq2eWp6Ik+bTp4GdkyhCmD1IHtq+Rurxl1uPp5XBJlKlZA2LL51Lb4uZjRM5AMAdY9z5c3gIKn4xEk/K27SG+nnqg+elHS+WSJF5u+NjU5LN2+PElau6rH9DU8q0kdnM+9MKUAchcjF021I40Axtg+FhuCP1+NSZdrVJ8zVdLww/l+t/tGYOOhbOU72u4TC32XwavFKPWjuXiJMUn4qpEGf6I+LgApqqB13sJE1kQfzN3bS9zq7nteeqDA5NRTA6CY47BTA7Aac8zjeupIS6IN3eJ7RNL3oM80tlGmiunWNQWAmO6xqAA2uurj1cflfL7FNPr57LI4A64Ny9eBPYDbLlKAsDNcvzDwOVVxTlWcUxX6OVXRszK1sDVBjiQVW6+zbk34xgknSlEAU2zGk6PYrwNeXI4C/txeV3B6FjmKkYx8GxkTs3bDFdswJ47VTg7Qnh1RDigypW85oMav/7iR54KRr/HIgi6XaDptmuGCKd3K5aW6NW7/BQ== ================================================ FILE: infra/diagrams/2.1/herbie-system.drawio ================================================ ================================================ FILE: infra/diff.rkt ================================================ #lang racket (require "../src/api/datafile.rkt") (define (field-equal? access t1 t2) (equal? (access t1) (access t2))) ;; Only interested in output ;; Only compare against runs with the same configuration (define (datafile-tests-equal? df1 df2) (define tests1 (report-info-tests df1)) (define tests2 (report-info-tests df2)) (and (= (length tests1) (length tests2)) (andmap (λ (t1 t2) (and (field-equal? table-row-name t1 t2) (field-equal? table-row-identifier t1 t2) (field-equal? table-row-output t1 t2) (field-equal? table-row-cost-accuracy t1 t2))) tests1 tests2))) (module+ main (command-line #:args (outdir1 outdir2) (define df1 (call-with-input-file (build-path outdir1 "results.json") read-datafile)) (define df2 (call-with-input-file (build-path outdir2 "results.json") read-datafile)) (cond [(datafile-tests-equal? df1 df2) (printf "Matching output expressions\n")] [else (printf "Output expressions do not match!!\n") (printf " datafile1: ~a\n" df1) (printf " datafile1: ~a\n" df2) (exit 1)]))) ================================================ FILE: infra/fidget2core.py ================================================ vars = set() nodes = [] op_parse = dict({ 'div':'/', 'mul':'*', 'add':'+', 'sub':'-', 'square':'pow', 'sqrt':'sqrt', 'exp':'exp', 'ln':'log', 'abs':'fabs', 'max':'fmax', 'min':'fmin', 'neg':'-', 'cos':'cos', 'sin':'sin'}) def parse_node(node): idx = int(node[0].replace('_', ''), 16) op = node[1] if 'var' in op: vars.add(op.replace('var-', '')) nodes.append([op.replace('var-', '')]) elif 'const' == op: nodes.append([node[2]]) elif op == 'square': nodes.append(['pow'] + [node[2]] + ['2']) else: op = op_parse[op] nodes.append([op] + node[2:]) with open("hi.vm", "r") as file: for line in file: if line[0] != '#' and line[0] != '\n': node = line.replace('\n', '').split(' ') parse_node(node) def recurse_nodes(idx): node = nodes[idx] if len(node) == 2: idx1 = int(node[1].replace('_', ''), 16) return '(' + node[0] + ' ' + recurse_nodes(idx1) + ')' elif len(node) == 3 and node[0] == 'pow' and node[2] == '2': idx1 = int(node[1].replace('_', ''), 16) return '(' + node[0] + ' ' + recurse_nodes(idx1) + ' ' + node[2] + ')' elif len(node) == 3: idx1 = int(node[1].replace('_', ''), 16) idx2 = int(node[2].replace('_', ''), 16) return '(' + node[0] + ' ' + recurse_nodes(idx1) + ' ' + recurse_nodes(idx2) + ')' else: return node[0] print(recurse_nodes(len(nodes)-1)) print(vars) ================================================ FILE: infra/herbie-demo.service ================================================ [Unit] Description=The Herbie web demo Documentation=https://herbie.uwplse.org/demo/ After=network.target [Service] ExecStart=make start-server WorkingDirectory=/var/www/herbie User=pavpan [Install] WantedBy=multi-user.target ================================================ FILE: infra/merge.rkt ================================================ #lang racket (require json) (require "../src/utils/common.rkt" "../src/utils/timeline.rkt" "../src/utils/profile.rkt" "../src/api/datafile.rkt" "../src/reports/timeline.rkt" "../src/reports/common.rkt" "../src/syntax/platform.rkt" "../src/syntax/load-platform.rkt") (define (merge-timelines outdir . dirs) (define tls (filter (conjoin (negate eof-object?) identity) (for/list ([dir (in-list dirs)]) (with-handlers ([exn? (const #f)]) (call-with-input-file (build-path outdir dir "timeline.json") read-json))))) (define info (call-with-input-file (build-path outdir (first dirs) "results.json") read-datafile)) (define joint-tl (apply timeline-merge tls)) (call-with-output-file (build-path outdir "timeline.json") #:exists 'replace (curry write-json joint-tl)) (call-with-output-file (build-path outdir "timeline.html") #:exists 'replace (λ (out) (write-html (make-timeline "Herbie run" joint-tl #:info info) out)))) (define (merge-profiles outdir . dirs) (define pfs (filter (conjoin (negate eof-object?) identity) (for/list ([dir (in-list dirs)]) (with-handlers ([exn? (const #f)]) (call-with-input-file (build-path outdir dir "profile.json") read-json))))) (define joint-pf (apply profile-merge (map json->profile pfs))) (call-with-output-file (build-path outdir "profile.json") #:exists 'replace (curry write-json (profile->json joint-pf)))) (define (merge-reports outdir . dirs) (define rss (filter (conjoin (negate eof-object?) identity) (for/list ([dir (in-list dirs)]) (with-handlers ([exn? (const #f)]) (define df (call-with-input-file (build-path outdir dir "results.json") read-datafile)) (if (eof-object? df) eof (cons df dir)))))) (define dfs (map car rss)) (define joint-rs (merge-datafiles dfs #:dirs dirs)) (write-datafile (build-path outdir "results.json") joint-rs) (copy-file (web-resource "report.html") (build-path outdir "index.html") #t)) (module+ main (command-line #:args (outdir . dirs) (activate-platform! (*platform-name*)) (apply merge-reports outdir dirs) (apply merge-timelines outdir dirs) (apply merge-profiles outdir dirs) (copy-file (web-resource "report.js") (build-path outdir "report.js") #t) (copy-file (web-resource "report-page.js") (build-path outdir "report-page.js") #t) (copy-file (web-resource "report.css") (build-path outdir "report.css") #t) (copy-file (web-resource "logo-car.png") (build-path outdir "logo-car.png") #t))) ================================================ FILE: infra/nightly.sh ================================================ #!/bin/bash # exit immediately upon first error, log every command executed set -e -x # Ensure egglog is in the path export PATH="$HOME/.cargo/bin/:$PATH" rustup update # Keep nightly installs isolated and consistent across install/run steps. export PLTADDONDIR="${PLTADDONDIR:-pltlibs}" make install # Seed is fixed for the whole day; this way two branches run the same seed SEED=$(date "+%Y%j") BRANCH=$(git rev-parse --abbrev-ref HEAD) BENCHDIR="$1"; shift REPORTDIR="$1"; shift if [[ "$BRANCH" == egglog-* ]]; then set -- "$@" --enable generate:egglog fi if [[ "$BRANCH" == rival2-* || "$BRANCH" == "rival2" ]]; then set -- "$@" --enable setup:rival2 fi mkdir -p "$REPORTDIR" rm -rf "reports"/* || echo "nothing to delete" # run dirs="" for bench in "$BENCHDIR"/*; do name=$(basename "$bench" .fpcore) rm -rf "$REPORTDIR"/"$name" racket -y "src/main.rkt" report \ --seed "$SEED" \ "$@" \ "$bench" "$REPORTDIR"/"$name" dirs="$dirs $name"; done # merge reports racket -y infra/merge.rkt "$REPORTDIR" $dirs ================================================ FILE: infra/softposit.rkt ================================================ #lang s-exp "../src/syntax/platform-language.rkt" ;;; Softposit platform, using David Thien's softposit-rkt package for ;;; bindings. Provides operations like real->posit16 or +.p16. (require math/bigfloat math/flonum softposit-rkt) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define posit8-max (ordinal->posit8 (- (expt 2 (- 8 1)) 1))) (define posit16-max (ordinal->posit16 (- (expt 2 (- 16 1)) 1))) (define posit32-max (ordinal->posit32 (- (expt 2 (- 32 1)) 1))) ;; Max quire is difficult to compute: ;; ;; A quire is a 2s-complement signed fixed-point number with ;; - `000...000` representing 0, ;; - `100...000` representing NAR. ;; Unlike traditional fixed-point numbers, quires are actually symmetric. ;; For an `n`-bit posit, the associated quire has bitwidth `16 * n` ;; with a scale of `2^(16 - 8*n)`. ;; ;; posit quire size max value nearest double w/ epsilon ;; -------|------------|----------------------|------------------------------------------| ;; 8 128 (2^(127) - 1) * 2^-48 2^79 - 2^-48 (6.044629098073146e+23) ;; 16 256 (2^(255) - 1) * 2^-112 2^143 - 2^-112 (1.1150372599265312e+43) ;; 32 512 (2^(511) - 1) * 2^-240 2^270 - 2^-240 (3.794275180128377e+81) ;; ;; Unfortunately, we don't have a good way to convert doubles to quire. ;; The libsoftposit library only has double to posit; the Racket shim ;; incorrectly composes double-posit, posit-quire conversions. ;; (define quire8-max (quire8-fdp-add (double->quire8 0.0) posit8-max posit8-max)) (define quire8-nmax (quire8-fdp-sub (double->quire8 0.0) posit8-max posit8-max)) (define quire16-max (quire16-fdp-add (double->quire16 0.0) posit16-max posit16-max)) (define quire16-nmax (quire16-fdp-sub (double->quire16 0.0) posit16-max posit16-max)) ; These crash ; (define quire32-max (quire32-fdp-add (double->quire32 0.0) posit32-max posit32-max)) ; (define quire32-nmax (quire32-fdp-sub (double->quire32 0.0) posit32-max posit32-max)) (define (bf-inf->nan x) (let ([y (bf x)]) (if (bfinfinite? y) +nan.bf y))) (define (double->posit8* x) (let ([y (double->posit8 x)]) (if (posit8= y (posit8-nar)) (if (> x 0) posit8-max (posit8-neg posit8-max)) y))) (define (double->posit16* x) (let ([y (double->posit16 x)]) (if (posit16= y (posit16-nar)) (if (> x 0) posit16-max (posit16-neg posit16-max)) y))) (define (double->posit32* x) (let ([y (double->posit32 x)]) (if (posit32= y (posit32-nar)) (if (> x 0) posit32-max (posit32-neg posit32-max)) y))) (define (double->quire8* x) (let ([y (double->quire8 x)]) (if (infinite? (quire8->double y)) (if (> x 0) quire8-max quire8-nmax) y))) (define (double->quire16* x) (let ([y (double->quire16 x)]) (if (infinite? (quire16->double y)) (if (> x 0) quire16-max quire16-nmax) y))) #;(define (double->quire32* x) (let ([y (double->quire32 x)]) (if (infinite? (quire32->double y)) (if (> x 0) quire32-max quire32-nmax) y))) (define (and-fn . as) (andmap identity as)) (define (or-fn . as) (ormap identity as)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; EMPTY PLATFORM ;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; REPRESENTATIONS ;;;;;;;;;;;;;;;;;;;;;;; (define (make-representation #:name 'posit8 #:bf->repr (compose double->posit8* bigfloat->flonum) #:repr->bf (compose bf-inf->nan posit8->double) #:ordinal->repr ordinal->posit8 #:repr->ordinal posit8->ordinal #:total-bits 8 #:special-value? (curry posit8= (posit8-nar)))) (define (make-representation #:name 'posit16 #:bf->repr (compose double->posit16* bigfloat->flonum) #:repr->bf (compose bf-inf->nan posit16->double) #:ordinal->repr ordinal->posit16 #:repr->ordinal posit16->ordinal #:total-bits 16 #:special-value? (curry posit16= (posit16-nar)))) (define (make-representation #:name 'posit32 #:bf->repr (compose double->posit32* bigfloat->flonum) #:repr->bf (compose bf-inf->nan posit32->double) #:ordinal->repr ordinal->posit32 #:repr->ordinal posit32->ordinal #:total-bits 32 #:special-value? (curry posit32= (posit32-nar)))) (define (make-representation #:name 'quire8 #:bf->repr (compose double->quire8* bigfloat->flonum) #:repr->bf (compose bf-inf->nan quire8->double) #:ordinal->repr (compose double->quire8 ordinal->flonum) #:repr->ordinal (compose flonum->ordinal quire8->double) #:total-bits 64 #:special-value? (const #f))) (define (make-representation #:name 'quire16 #:bf->repr (compose double->quire16* bigfloat->flonum) #:repr->bf (compose bf-inf->nan quire16->double) #:ordinal->repr (compose double->quire16 ordinal->flonum) #:repr->ordinal (compose flonum->ordinal quire16->double) #:total-bits 64 #:special-value? (const #f))) (define (make-representation #:name 'quire32 #:bf->repr (compose double->quire32 bigfloat->flonum) ; TODO: use double->quire32* when crash fixed #:repr->bf (compose bf-inf->nan quire32->double) #:ordinal->repr (compose double->quire32 ordinal->flonum) #:repr->ordinal (compose flonum->ordinal quire32->double) #:total-bits 64 #:special-value? (const #f))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BOOLEAN IMPLS ;;;;;;;;;;;;;;;;;;;;;;;;; (define-representation #:cost 1) (define-operations () [TRUE #:spec (TRUE) #:impl (const true) #:fpcore TRUE #:cost 1] [FALSE #:spec (FALSE) #:impl (const false) #:fpcore FALSE #:cost 1]) (define-operations ([x ] [y ]) [and #:spec (and x y) #:impl (lambda v (andmap values v)) #:cost 1] [or #:spec (or x y) #:impl (lambda v (ormap values v)) #:cost 1]) (define-operation (not [x ]) #:spec (not x) #:impl not #:cost 1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; POSIT IMPLS ;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-representation #:cost 1) (define-operation (if.p8 [c ] [t ] [f ]) #:spec (if c t f) #:impl if-impl #:cost (if-cost 1)) (define-representation #:cost 1) (define-operation (if.p16 [c ] [t ] [f ]) #:spec (if c t f) #:impl if-impl #:cost (if-cost 1)) (define-representation #:cost 1) (define-operation (if.p32 [c ] [t ] [f ]) #:spec (if c t f) #:impl if-impl #:cost (if-cost 1)) (define-operations ([x ] [y ]) [==.p8 #:spec (== x y) #:impl posit8= #:cost 1] [<.p8 #:spec (< x y) #:impl posit8< #:cost 1] [>.p8 #:spec (> x y) #:impl posit8> #:cost 1] [<=.p8 #:spec (<= x y) #:impl posit8<= #:cost 1] [>=.p8 #:spec (>= x y) #:impl posit8>= #:cost 1]) (define-operations ([x ]) #:fpcore (! :precision posit8 _) [neg.p8 #:spec (neg x) #:impl posit8-neg #:fpcore (- x) #:cost 1] [sqrt.p8 #:spec (sqrt x) #:impl posit8-sqrt #:cost 1]) (define-operations ([x ] [y ]) #:fpcore (! :precision posit8 _) [+.p8 #:spec (+ x y) #:impl posit8-add #:cost 1] [-.p8 #:spec (- x y) #:impl posit8-sub #:cost 1] [*.p8 #:spec (* x y) #:impl posit8-mul #:cost 1] [/.p8 #:spec (/ x y) #:impl posit8-div #:cost 1]) (define-operations ([x ] [y ]) [==.p16 #:spec (== x y) #:impl posit16= #:cost 1] [<.p16 #:spec (< x y) #:impl posit16< #:cost 1] [>.p16 #:spec (> x y) #:impl posit16> #:cost 1] [<=.p16 #:spec (<= x y) #:impl posit16<= #:cost 1] [>=.p16 #:spec (>= x y) #:impl posit16>= #:cost 1]) (define-operations ([x ]) #:fpcore (! :precision posit16 _) [neg.p16 #:spec (neg x) #:impl posit16-neg #:fpcore (- x) #:cost 1] [sqrt.p16 #:spec (sqrt x) #:impl posit16-sqrt #:cost 1]) (define-operations ([x ] [y ]) #:fpcore (! :precision posit16 _) [+.p16 #:spec (+ x y) #:impl posit16-add #:cost 1] [-.p16 #:spec (- x y) #:impl posit16-sub #:cost 1] [*.p16 #:spec (* x y) #:impl posit16-mul #:cost 1] [/.p16 #:spec (/ x y) #:impl posit16-div #:cost 1]) (define-operations ([x ] [y ]) [==.p32 #:spec (== x y) #:impl posit32= #:cost 1] [<.p32 #:spec (< x y) #:impl posit32< #:cost 1] [>.p32 #:spec (> x y) #:impl posit32> #:cost 1] [<=.p32 #:spec (<= x y) #:impl posit32<= #:cost 1] [>=.p32 #:spec (>= x y) #:impl posit32>= #:cost 1]) (define-operations ([x ]) #:fpcore (! :precision posit32 _) [neg.p32 #:spec (neg x) #:impl posit32-neg #:fpcore (- x) #:cost 1] [sqrt.p32 #:spec (sqrt x) #:impl posit32-sqrt #:cost 1]) (define-operations ([x ] [y ]) #:fpcore (! :precision posit32 _) [+.p32 #:spec (+ x y) #:impl posit32-add #:cost 1] [-.p32 #:spec (- x y) #:impl posit32-sub #:cost 1] [*.p32 #:spec (* x y) #:impl posit32-mul #:cost 1] [/.p32 #:spec (/ x y) #:impl posit32-div #:cost 1]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; QUIRE OPERATIONS ;;;;;;;;;;;;;;;;;;;;;;; (define-representation #:cost 1) (define-representation #:cost 1) (define-representation #:cost 1) (define-operations ([x ] [y ] [z ]) #:fpcore (! :precision quire8 _) [quire8-mul-add #:spec (+ x (* y z)) #:impl quire8-fdp-add #:fpcore (fdp x y z) #:cost 1] [quire8-mul-sub #:spec (- x (* y z)) #:impl quire8-fdp-sub #:fpcore (fds x y z) #:cost 1]) (define-operations ([x ] [y ] [z ]) #:fpcore (! :precision quire16 _) [quire16-mul-add #:spec (+ x (* y z)) #:impl quire16-fdp-add #:fpcore (fdp x y z) #:cost 1] [quire16-mul-sub #:spec (- x (* y z)) #:impl quire16-fdp-sub #:fpcore (fds x y z) #:cost 1]) (define-operations ([x ] [y ] [z ]) #:fpcore (! :precision quire32 _) [quire32-mul-add #:spec (+ x (* y z)) #:impl quire32-fdp-add #:fpcore (fdp x y z) #:cost 1] [quire32-mul-sub #:spec (- x (* y z)) #:impl quire32-fdp-sub #:fpcore (fds x y z) #:cost 1]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; CONVERTERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-representation #:cost 1) (define-operation (binary64->posit8 [x ]) #:spec x #:impl double->posit8 #:fpcore (! :precision posit8 (cast x)) #:cost 1) (define-operation (binary64->posit16 [x ]) #:spec x #:impl double->posit16 #:fpcore (! :precision posit16 (cast x)) #:cost 1) (define-operation (binary64->posit32 [x ]) #:spec x #:impl double->posit32 #:fpcore (! :precision posit32 (cast x)) #:cost 1) (define-operation (posit8->binary64 [x ]) #:spec x #:impl posit8->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (posit16->binary64 [x ]) #:spec x #:impl posit16->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (posit32->binary64 [x ]) #:spec x #:impl posit32->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (binary64->quire8 [x ]) #:spec x #:impl double->quire8 #:fpcore (! :precision quire8 (cast x)) #:cost 1) (define-operation (binary64->quire16 [x ]) #:spec x #:impl double->quire16 #:fpcore (! :precision quire16 (cast x)) #:cost 1) (define-operation (binary64->quire32 [x ]) #:spec x #:impl double->quire32 #:fpcore (! :precision quire32 (cast x)) #:cost 1) (define-operation (quire8->binary64 [x ]) #:spec x #:impl quire8->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (quire16->binary64 [x ]) #:spec x #:impl quire16->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (quire32->binary64 [x ]) #:spec x #:impl quire32->double #:fpcore (! :precision binary64 (cast x)) #:cost 1) (define-operation (quire8->posit8 [x ]) #:spec x #:impl quire8->posit8 #:fpcore (! :precision posit8 (cast x)) #:cost 1) (define-operation (quire16->posit16 [x ]) #:spec x #:impl quire16->posit16 #:fpcore (! :precision posit16 (cast x)) #:cost 1) (define-operation (quire32->posit32 [x ]) #:spec x #:impl quire32->posit32 #:fpcore (! :precision posit32 (cast x)) #:cost 1) (define-operation (posit8->quire8 [x ]) #:spec x #:impl posit8->quire8 #:fpcore (! :precision quire8 (cast x)) #:cost 1) (define-operation (posit16->quire16 [x ]) #:spec x #:impl posit16->quire16 #:fpcore (! :precision quire16 (cast x)) #:cost 1) (define-operation (posit32->quire32 [x ]) #:spec x #:impl posit32->quire32 #:fpcore (! :precision quire32 (cast x)) #:cost 1) ================================================ FILE: infra/sort-fpcore.rkt ================================================ #lang racket (define (read-lines port) (define line (read port)) (if (equal? line eof) empty (cons line (read-lines port)))) (define (fpcore-less-than fpcore fpcore2) (string>? (~a (rest (rest fpcore))) (~a (rest (rest fpcore2))))) (define (sort-fpcores fpcores) (sort fpcores fpcore-less-than)) (define (no-casts expr) (cond [(list? expr) (andmap no-casts expr)] [else (not (equal? expr '!))])) (module+ main (command-line #:program "sort" #:args (json-file output-file) (define filtered (filter no-casts (sort-fpcores (read-lines (open-input-file json-file))))) (unless (empty? filtered) (define output (open-output-file output-file #:exists 'replace)) (for ([line filtered]) (write line output))))) ================================================ FILE: infra/survey/.gitignore ================================================ .DS_Store .env ================================================ FILE: infra/survey/README.md ================================================ # Dependencies * [jq](https://stedolan.github.io/jq/) * [matplotlib](https://matplotlib.org/) * [numpy](https://numpy.org/) # Optional dependencies * [env_parallel](https://www.gnu.org/software/parallel/env_parallel.html) ================================================ FILE: infra/survey/seed-variance.sh ================================================ #!/usr/bin/env bash # exit immediately upon first error set -e # determine physical directory of this script src="${BASH_SOURCE[0]}" while [ -L "$src" ]; do dir="$(cd -P "$(dirname "$src")" && pwd)" src="$(readlink "$src")" [[ $src != /* ]] && src="$dir/$src" done MYDIR="$(cd -P "$(dirname "$src")" && pwd)" # Herbie path HERBIE="$MYDIR/../.." # handle inputs if [ "$#" -ne 3 ]; then echo "Usage: $0 NUM_SEEDS BENCH_PATH RESULT_PATH" exit 1 else NSEEDS="$1"; shift BENCH="$1"; shift RESPATH="$1"; shift fi echo "Running on benchmarks at $HERBIE/$BENCH" echo "Writing results to $RESPATH" # advise user of threads if [ -z "$THREADS" ]; then THREADS="yes" echo "Using maximum number of threads" else # support for exporting bash environment to parallel echo "Using $THREADS threads for each run" fi # advise user of execution plan if [ -z "$PARALLEL_SEEDS" ]; then echo "Using Herbie concurrency only." else # support for exporting bash environment to parallel source $(which env_parallel.bash) env_parallel --record-env echo "Using multiple concurrent Herbie runs in parallel." echo "Restricting to $PARALLEL_SEEDS parallel concurrent Herbie runs." fi # # SAMPLE SEEDS # # allocate space for output tstamp="$(date "+%Y-%m-%d_%H%M")" output="$(pwd)/$RESPATH/$tstamp" mkdir -p "$output" function do_seed { seed="$1" seed_output="$output/$(printf "%03d" "$seed")" mkdir -p "$seed_output" racket -y "$HERBIE/src/main.rkt" report \ --threads $THREADS \ --seed "$seed" \ $HERBIE_FLAGS \ "$HERBIE/$BENCH" \ "$seed_output" } # sample herbie behavior if [ -z "$PARALLEL_SEEDS" ]; then # by default, do not use parallel for seed in $(seq "$NSEEDS"); do do_seed "$seed" done else # conditionally use parallel # # Note that Herbie can already use up to # of benchmarks cores, # so this probably only makes sense if you have PARALLEL_SEEDS # set to something less than # of cores divided by # of benchmarks, # i.e., you have a lot of cores. We're not at all careful to get # solid timing numbers, but going higher any than that will make # any time measurements even less meaningful. seq "$NSEEDS" \ | env_parallel \ --env _ \ --jobs "$PARALLEL_SEEDS" \ --halt now,fail=1 \ do_seed fi # # COLLECT OUTPUT # pushd "$output" echo "[" > all.json first=true for rj in $(find . -name 'results.json' | sort); do if $first; then first=false else echo "," >> all.json fi seed="$(jq '.seed' "$rj")" npts="$(jq '.points' "$rj")" herbie_iters="$(jq '.iterations' "$rj")" # warn about errors and timeouts that will be filtered out errors="$(jq '.tests | map(select(.status == "error"))' "$rj")" if [ "$errors" != "[]" ]; then echo "WARNING: filtering out errors in $rj on seed $seed" echo "$errors" echo "$seed" >> errors.json echo "$errors" >> errors.json fi timeouts="$(jq '.tests | map(select(.status == "timeout"))' "$rj")" if [ "$timeouts" != "[]" ]; then echo "WARNING: filtering out timeouts in $rj on seed $seed" echo "$timeouts" echo "$seed" >> timeouts.json echo "$timeouts" >> timeouts.json fi cat "$rj" \ | jq --argjson SEED "$seed" \ --argjson NPTS "$npts" \ --argjson HERBIE_ITERS "$herbie_iters" \ '.tests | map( select(.status != "error") | select(.status != "timeout") | { "test" : .name , "input" : .input , "output" : .output , "output_parens" : (.output | [match("[(]"; "g")] | length) , "avg_bits_err_input": .start , "avg_bits_err_output": .end , "avg_bits_err_improve": (.start - .end) , "time_improve": .time , "seed": $SEED , "npts": $NPTS , "herbie_iters": $HERBIE_ITERS })' \ >> all.json done echo "]" >> all.json # flatten array of array of results to an array jq 'flatten' all.json > all.json.tmp mv all.json.tmp all.json popd # # PLOT RESULTS # $MYDIR/src/plot-results.sh "$output" ================================================ FILE: infra/survey/src/plot-results.sh ================================================ #!/usr/bin/env bash # determine physical directory of this script src="${BASH_SOURCE[0]}" while [ -L "$src" ]; do dir="$(cd -P "$(dirname "$src")" && pwd)" src="$(readlink "$src")" [[ $src != /* ]] && src="$dir/$src" done MYDIR="$(cd -P "$(dirname "$src")" && pwd)" # caller should pass path to output from sampler cd "$1" if [ ! -f "all.json" ]; then echo "ERROR: no 'all.json' in '$1'" exit 1 fi # munge and plot results jq 'group_by(.seed)' all.json > by-seed.json jq 'group_by(.test)' all.json > by-test.json function plot-seed-field { field="$1" cat by-seed.json \ | jq --arg FIELD "$field" \ 'map({ "seed": .[0].seed , "data": map(.[$FIELD]) | add })' \ > "by-seed-${field}.json" python3 "$MYDIR/seed-violin-plot.py" \ "by-seed-${field}.json" \ "$field" python3 "$MYDIR/seed-bar-chart.py" \ "by-seed-${field}.json" \ "$field" } function plot-test-field { field="$1" cat by-test.json \ | jq --arg FIELD "$field" \ 'map({ "test": .[0].test , "data": map(.[$FIELD]) })' \ > "by-test-${field}.json" python3 "$MYDIR/test-violin-plot.py" \ "by-test-${field}.json" \ "$field" } function plot-test-versus { x="$1" y="$2" cat by-test.json \ | jq --arg X "$x" --arg Y "$y" \ 'map({ "test": .[0].test , "x": map(.[$X]) , "y": map(.[$Y]) })' \ > "by-test-${x}-versus-${y}.json" python3 "$MYDIR/test-versus-plot.py" \ "by-test-${x}-versus-${y}.json" \ "$x" "$y" } fields=" output_parens avg_bits_err_input avg_bits_err_output avg_bits_err_improve time_improve" for f in $fields; do plot-seed-field "$f" done for f in $fields; do plot-test-field "$f" done mkdir "versus-plots" plot-test-versus "time_improve" "avg_bits_err_output" plot-test-versus "time_improve" "output_parens" plot-test-versus "output_parens" "avg_bits_err_output" ## plot-test-versus "time_improve" "avg_bits_err_improve" ## plot-test-versus "output_parens" "avg_bits_err_improve" ================================================ FILE: infra/survey/src/seed-bar-chart.py ================================================ import sys import json import matplotlib.pyplot as plt import numpy as np jsonp = sys.argv[1] field = sys.argv[2] with open(jsonp, 'r') as f: ss = json.load(f) labs = [s['seed'] for s in ss] data = [s['data'] for s in ss] fig, ax = plt.subplots(figsize=(30, 10)) x = np.arange(len(data)) ax.bar(x, data) ax.set_xlabel('seed') ax.set_xticks(x) ax.set_xticklabels(range(1, len(labs) + 1)) ax.set_xticklabels(labs, rotation=45, ha='right') ax.set_ylabel('sum({})'.format(field)) plt.tight_layout() plt.savefig('by-seed-{}-bar.pdf'.format(field)) ================================================ FILE: infra/survey/src/seed-violin-plot.py ================================================ import sys import json import matplotlib.pyplot as plt import numpy as np jsonp = sys.argv[1] field = sys.argv[2] with open(jsonp, 'r') as f: ss = json.load(f) labs = [s['seed'] for s in ss] data = [s['data'] for s in ss] fig, ax = plt.subplots() ax.violinplot(data) ax.set_xlabel('{} seeds'.format(len(data))) ax.set_ylabel('sum({})'.format(field)) plt.tight_layout() plt.savefig('by-seed-{}-violin.pdf'.format(field)) ================================================ FILE: infra/survey/src/test-versus-plot.py ================================================ import sys import json import matplotlib.pyplot as plt import numpy as np jsonp = sys.argv[1] x_field = sys.argv[2] y_field = sys.argv[3] with open(jsonp, 'r') as f: ts = json.load(f) for t in ts: fig, ax = plt.subplots(figsize=(10,10)) ax.scatter(t['x'], t['y']) ax.set_title('{}: {} vs. {}'.format(t['test'], x_field, y_field)) ax.set_xlabel(x_field) ax.set_ylabel(y_field) plt.tight_layout() p = 'versus-plots/by-test-{}-{}-versus-{}.pdf'.format( t['test'], x_field, y_field) plt.savefig(p) plt.close(fig) ================================================ FILE: infra/survey/src/test-violin-plot.py ================================================ import sys import json import matplotlib.pyplot as plt import numpy as np jsonp = sys.argv[1] field = sys.argv[2] with open(jsonp, 'r') as f: ts = json.load(f) labs = [t['test'] for t in ts] data = [t['data'] for t in ts] fig, ax = plt.subplots(figsize=(30,10)) ax.violinplot(data) ax.set_xlabel('test') ax.set_xticks(np.arange(1, len(labs) + 1)) ax.set_xticklabels(labs, rotation=45, ha='right') ax.set_ylabel(field) ax.set_yticks(np.arange(0, 64, step=4)) plt.tight_layout() plt.savefig('by-test-{}-violin.pdf'.format(field)) ================================================ FILE: infra/survey.rkt ================================================ #lang racket (require json xml) (require "../src/api/datafile.rkt") (define metrics (list `(start . ,table-row-start) `(end . ,table-row-result) `(time . ,table-row-time))) (define (merge-results . dirs) (define results (filter (conjoin (negate eof-object?) identity) (for/list ([dir (in-list dirs)]) (with-handlers ([exn? (const #f)]) (call-with-input-file (build-path dir "results.json") read-datafile))))) (for/hash ([group (group-by table-row-name (append-map report-info-tests results))]) (values (table-row-name (first group)) (for/hash ([(name metric) (in-dict metrics)]) (values name (map metric group)))))) (define style " section { overflow: auto; } h2 { font-size: 100%; font-family: sans-serif; margin: 3em 0 0; } h3 { text-transform: uppercase; font-size: 80%; color: steelblue} section > div { width: 500; float: left; margin-right: 20px; } ") (define (results->html results out) (fprintf out "\n") (write-xexpr `(html (head (meta ([charset "utf-8"])) (title "Seed survey results") (style ,(make-cdata #f #f style)) (script ([src "report.js"]))) (body (h1 ,(format "Seed survey for ~a benchmarks" (hash-count results))) ,@ (for/list ([(name metrics) (in-dict results)] [n (in-naturals)]) `(section (h2 ,(~a name)) ,@ (for/list ([(name values) (in-dict metrics)]) `(div (h3 ,(~a name)) (canvas ([id ,(format "~a-~a" name n)] [title "Weighted histogram; height corresponds to percentage of runtime in that bucket."])) (script "histogram(\"" ,(format "~a-~a" name n) "\", " ,(jsexpr->string values) ", " ,(jsexpr->string (hash 'width 400 'proportional #f 'buckets 32.0 'max (if (equal? name 'time) (json-null) 64.0))) ")"))))))) out)) (module+ main (command-line #:args (outdir . dirs) (define data (apply merge-results dirs)) (printf "Read in data on ~a items\n" (hash-count data)) (call-with-output-file (build-path outdir "survey.html") #:exists 'replace (λ (p) (results->html data p))))) ================================================ FILE: infra/test-api.mjs ================================================ // -*- mode: js -*- import { strict as assert } from 'node:assert'; // use strict equality everywhere import { spawn } from 'child_process'; import net from 'net'; /* Step 1: we start the Herbie server for testing */ function getFreePort() { return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(0, () => { const { port } = server.address(); server.close(() => resolve(port)); }); server.on('error', reject); }); } const PORT = await getFreePort(); console.log("Spawning server on port " + PORT) const child = spawn('racket', ['-y', 'src/main.rkt', 'web', '--threads', '2', '--quiet', '--port', ""+PORT]); child.stdout.on('data', (data) => { console.log(""+data); }); child.stderr.on('data', (data) => { console.error(""+data); }); child.on('close', (code) => { if (code) console.log("Server crashed with code " + code); }); process.on('exit', () => { child.kill('SIGTERM'); // or any appropriate signal }); function waitForPort(port) { return new Promise(resolve => { const attempt = () => { const socket = net.connect(port, 'localhost', () => { socket.end(); resolve(); }); socket.on('error', () => setTimeout(attempt, 500)); }; attempt(); }); } await waitForPort(PORT) console.log("Server up and responding on port " + PORT) function makeURL(endpoint) { return new URL(`http://127.0.0.1:${PORT}${endpoint}`) } /* Step 2: Test the formal API */ // Reusable testing data const SAMPLE_SIZE = 8256 const FPCoreFormula = '(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))' const FPCoreFormula2 = '(FPCore (x) (- (sqrt (+ x 1))))' const FPCoreFormula3 = '(FPCore (x) (if (<= (- (sqrt (+ x 1.0)) (sqrt x)) 0.05) (* 0.5 (sqrt (/ 1.0 x))) (fma (fma (- 0.125) x 0.5) x (- 1.0 (sqrt x)))))' const eval_sample = [[[1], -1.4142135623730951]] async function testAPI(url, body, cb) { console.log("Testing endpoint " + url) let sync_start = Date.now() const sync_resp = await fetch(makeURL(url), { method: 'POST', body: JSON.stringify(body), }); assert.ok(sync_resp.headers.get("x-herbie-job-id")); const sync_json = await sync_resp.json(); assert.equal(sync_json.job.length > 0, true) assert.equal(sync_json.path.includes("."), true) console.log(" Synchronous response in " + Math.round(Date.now() - sync_start) + "ms") cb(sync_json); let async_start = Date.now() let start_resp = await fetch(makeURL(url.replace("/api", "/api/start")), { method: 'POST', body: JSON.stringify(body), }) let jobid = (await start_resp.json()).job; // This loop is a sort of 10 second timeout for the test. for (let i = 0; i < 100; i++) { let status_resp = await fetch(makeURL("/check-status/" + jobid)); if (status_resp.status == 201) break; await new Promise(r => setTimeout(r, 100)); // Wait 100ms } let async_resp = await fetch(makeURL("/api/result/" + jobid)); let async_json = await async_resp.json(); console.log(" Asynchronous response in " + Math.round(Date.now() - async_start) + "ms") cb(async_json); } let POINTS = null; // Sample endpoint await testAPI("/api/sample", { formula: FPCoreFormula2, seed: 5, }, (body) => { assert.ok(body.points); assert.equal(body.points.length, SAMPLE_SIZE); if (!POINTS) { POINTS = body.points; } else { // Test reproducibility between sync and async call assert.deepEqual(POINTS, body.points); } }); // Explanations endpoint await testAPI("/api/explanations", { formula: FPCoreFormula, sample: POINTS }, (body) => { assert.equal(body.explanation.length > 0, true, 'explanation should not be empty'); }); // Analyze endpoint await testAPI("/api/analyze", { formula: FPCoreFormula, sample: [[[14.97651307489794], 0.12711304680349078]] }, (body) => { assert.deepEqual(body.points, [[[14.97651307489794], "2.3"]]); }); // Local error endpoint await testAPI("/api/localerror", { formula: FPCoreFormula, sample: eval_sample }, (body) => { assert.ok(body.tree['avg-error'] > 0); }); const localError1 = await (await fetch(makeURL("/api/localerror"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula, sample: [[[2.852044568544089e-150], 1e+308]], seed: 5 }) })).json() const localError2 = await (await fetch(makeURL("/api/localerror"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula, sample: [[[1.5223342548065899e-15], 1e+308]], seed: 5 }) })).json() // Test that different sample points produce different job ids ensuring that different results are served for these inputs. assert.notEqual(localError1.job, localError2.job) // Assert local error works for default example. const ignoredValue = 1e+308 '(FPCore (1e-100) (- (sqrt (+ x 1)) (sqrt x)))' const localError5 = await (await fetch(makeURL("/api/localerror"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula, sample: [[[1e-100], ignoredValue]], seed: 5 }) })).json() // avg_error, actual_value, exact_value, absolute_difference, ulps_error // root node checkLocalErrorNode(localError5.tree, [], '-', '0.0', '1.0', '1.0', '1e-50', '0.0') // left sqrt checkLocalErrorNode(localError5.tree, [0], 'sqrt', '0.0', '1.0', '1.0', '5e-101', '0.0') // right sqrt checkLocalErrorNode(localError5.tree, [1], 'sqrt', '0.0', '1e-50', '1e-50', '2.379726195519099e-68', '0.0') // plus checkLocalErrorNode(localError5.tree, [0, 0], '+', '0.0', '1.0', '1.0', '1e-100', '0.0') // var x checkLocalErrorNode(localError5.tree, [0, 0, 0], 'x', '0.0', '1e-100', '1e-100', 'equal', '0.0') // literal 1 checkLocalErrorNode(localError5.tree, [0, 0, 1], '1.0', '0.0', '1.0', '1.0', 'equal', '0.0') // '(FPCore (1e100) (- (sqrt (+ x 1)) (sqrt x)))' const localError6 = await (await fetch(makeURL("/api/localerror"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula, sample: [[[1e100], ignoredValue]], seed: 5 }) })).json() // avg_error, actual_value, exact_value, absolute_error, ulps_error // root node checkLocalErrorNode(localError6.tree, [], '-', '61.7', '0.0', '5e-51', '5e-51', '61.74124908607812') // left sqrt checkLocalErrorNode(localError6.tree, [0], 'sqrt', '0.0', '1e+50', '1e+50', '6.834625285603891e+33', '0.0') // right sqrt checkLocalErrorNode(localError6.tree, [1], 'sqrt', '0.0', '1e+50', '1e+50', '6.834625285603891e+33', '0.0') // plus checkLocalErrorNode(localError6.tree, [0, 0], '+', '0.0', '1e+100', '1e+100', '1.0', '0.0') // var x checkLocalErrorNode(localError6.tree, [0, 0, 0], 'x', '0.0', '1e+100', '1e+100', 'equal', '0.0') // literal 1 checkLocalErrorNode(localError6.tree, [0, 0, 1], '1.0', '0.0', '1.0', '1.0', 'equal', '0.0') // Test a large number `2e269` to trigger NaNs in local error const localError7 = await (await fetch(makeURL("/api/localerror"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula3, sample: [[[2e269], ignoredValue]], seed: 5 }) })).json() // Test against conditionals expressions checkLocalErrorNode(localError7.tree, [0], '<=', '0.0', 'true', 'true', 'equal', '0.0') checkLocalErrorNode(localError7.tree, [0, 0], '-', '61.2', '0.0', '1.1180339887498948e-135', '1.1180339887498948e-135', '61.16647760559045') checkLocalErrorNode(localError7.tree, [0, 1], '0.05', '0.0', '0.05', '0.05', 'equal', '0.0') checkLocalErrorNode(localError7.tree, [2], 'fma', '0.0', '-inf.0', '-inf.0', '+inf.0', '0.0') /// root: The root node of the local error tree. /// path: the path to get to the node you want to test. /// name: Name of the node you are testing /// avg_error: Average Error /// actual_value: Value of the node /// exact_value: The correct evaluation of the expression /// absolute_difference: The ABS of the error at the node |approx - exact| /// ulps_error: ulps of error at this node. function checkLocalErrorNode(root, path, name, avg_error, actual_value, exact_value, absolute_difference, ulps_error) { const node = getNodeFromPath(root, path) // console.log(node) // Helpful for seeing which node is failing a test assert.equal(node['e'], name) assert.equal(node['avg-error'], avg_error) assert.equal(node['actual-value'], actual_value) assert.equal(node['exact-value'], exact_value) assert.equal(node['abs-error-difference'], absolute_difference) assert.equal(node['ulps-error'], ulps_error) } function getNodeFromPath(node, path) { if (path.length > 0) { const index = path.shift() const child = node['children'][index] return getNodeFromPath(child, path) } else { return node } } // Alternatives endpoint await testAPI("/api/alternatives", { formula: FPCoreFormula, sample: [[[14.97651307489794], 0.12711304680349078]] }, (body) => { assert.ok(Array.isArray(body.alternatives)); }); // Cost endpoint await testAPI("/api/cost", { formula: FPCoreFormula2, sample: eval_sample }, (body) => { assert.ok(body.cost > 0); }); // MathJS endpoint const mathjs = await (await fetch(makeURL("/api/mathjs"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula }) })).json() assert.equal(mathjs.mathjs, "sqrt(x + 1.0) - sqrt(x)") console.log("Testing translation API"); async function testTranslate(language, result) { let start = Date.now() const resp = await fetch(makeURL("/api/translate"), { method: 'POST', body: JSON.stringify({ formula: FPCoreFormula, language: language, }), }); const json = await resp.json(); console.log(" Translation to " + language + " in " + Math.round(Date.now() - start) + "ms") assert.equal(json.result.trim().replace("\t", " "), result.trim()); } await testTranslate("python", ` def expr(x): return math.sqrt((x + 1.0)) - math.sqrt(x)`); await testTranslate("c", ` double expr(double x) { return sqrt((x + 1.0)) - sqrt(x); }`); await testTranslate("fortran", ` real(8) function expr(x) use fmin_fmax_functions real(8), intent (in) :: x expr = sqrt((x + 1.0d0)) - sqrt(x) end function`); await testTranslate("java", ` public static double expr(double x) { return Math.sqrt((x + 1.0)) - Math.sqrt(x); }`); await testTranslate("julia", ` function expr(x) return Float64(sqrt(Float64(x + 1.0)) - sqrt(x)) end`); await testTranslate("matlab", ` function tmp = expr(x) tmp = sqrt((x + 1.0)) - sqrt(x); end`); await testTranslate("wls", 'expr[x_] := N[(N[Sqrt[N[(x + 1), $MachinePrecision]], $MachinePrecision] - N[Sqrt[x], $MachinePrecision]), $MachinePrecision]') await testTranslate("tex", '\\mathsf{expr}\\left(x\\right) = \\sqrt{x + 1} - \\sqrt{x}') /* Step 3: Test the legacy HTTP endpoints */ console.log("Testing legacy endpoints") // improve endpoint const improveResponse = await fetch(makeURL(`/improve?formula=${encodeURIComponent(FPCoreFormula2)}`), { method: 'GET' }) assert.equal(improveResponse.status, 200) let redirect = improveResponse.url.split("/") const jobID = redirect[3].split(".")[0] // This test is a little flaky as the character count of the response is not consistent. // const improveHTML = await improveResponse.text() // const improveHTMLexpectedCount = 25871 // assert.equal(improveHTML.length, improveHTMLexpectedCount, `HTML response character count should be ${improveHTMLexpectedCount} unless HTML changes.`) // timeline const timelineRSP = await fetch(makeURL(`/timeline/${jobID}`), { method: 'GET' }) assert.equal(timelineRSP.status, 201) const timeline = await timelineRSP.json() assert.equal(timeline.length > 0, true) // Test with a likely missing job-id const badTimelineRSP = await fetch(makeURL("/timeline/42069"), { method: 'GET' }) assert.equal(badTimelineRSP.status, 404) const check_missing_job = await fetch(makeURL(`/check-status/42069`), { method: 'GET' }) assert.equal(check_missing_job.status, 202) // improve-start endpoint const URIencodedBody = "formula=" + encodeURIComponent(FPCoreFormula) const startResponse = await fetch(makeURL("/api/start/improve"), { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: URIencodedBody }) const testResult = (startResponse.status == 201 || startResponse.status == 202) assert.equal(testResult, true) const improveResultPath = startResponse.headers.get("location") let counter = 0 let cap = 100 // Check status endpoint let checkStatus = await fetch(makeURL(improveResultPath), { method: 'GET' }) /* This is testing if the /api/start/improve test at the beginning has been completed. The cap and counter is a sort of timeout for the test. Ends up being 10 seconds max. */ while (checkStatus.status != 201 && counter < cap) { counter += 1 checkStatus = await fetch(makeURL(improveResultPath), { method: 'GET' }) await new Promise(r => setTimeout(r, 100)); // ms } assert.equal(checkStatus.statusText, 'Job complete') // up endpoint const up = await fetch(makeURL("/up"), { method: 'GET' }) assert.equal('Up', up.statusText) // Herbie runs single thread on CI. // Results.json endpoint const jsonResults = await (await fetch(makeURL("/results.json"), { method: 'GET' })).json() // Basic test that checks that there are the two results after the above test. // TODO add a way to reset the results.json file? assert.equal(jsonResults.tests.length, 2) child.kill('SIGINT'); ================================================ FILE: src/api/datafile.rkt ================================================ #lang racket (require json racket/date) (require "../syntax/platform.rkt" "../syntax/types.rkt" "../utils/common.rkt" "../reports/data.rkt") (provide (struct-out table-row) (struct-out report-info) make-report-info read-datafile write-datafile merge-datafiles) (define (make-report-info tests #:seed [seed #f]) (report-info (current-date) *herbie-commit* *herbie-branch* (or seed (get-seed)) (*flags*) (*num-points*) (*num-iterations*) tests)) (define (write-datafile file info) (define (simplify-test test) (match-define (table-row name identifier status pre prec conversions vars warnings input output spec target-prog start-bits end-bits target-bits time link cost-accuracy) test) (define bits (representation-total-bits (get-representation prec))) (make-hash `((name . ,name) (identifier . ,(~s identifier)) (pre . ,(~s pre)) (prec . ,(~s prec)) (bits . ,(representation-total-bits (get-representation prec))) (conversions . ,(map (curry map ~s) conversions)) (status . ,status) (start . ,start-bits) (end . ,end-bits) (target . ,target-bits) (vars . ,(and vars (map symbol->string vars))) (warnings . ,warnings) (input . ,(~s input)) (output . ,(~s output)) (spec . ,(if (equal? spec input) #f (~s spec))) (target-prog . ,(~s target-prog)) (time . ,time) (link . ,(~a link)) (cost-accuracy . ,cost-accuracy)))) (define data (match info [(report-info date commit branch seed flags points iterations tests) (make-hash `((date . ,(date->seconds date)) (commit . ,commit) (branch . ,branch) (seed . ,(~a seed)) (flags . ,(flags->list flags)) (points . ,points) (iterations . ,iterations) (tests . ,(map simplify-test tests))))])) (if (port? file) (write-json data file) (call-with-atomic-output-file file (λ (p name) (write-json data p))))) (define (flags->list flags) (for*/list ([rec (hash->list flags)] [fl (cdr rec)]) (format "~a:~a" (car rec) fl))) (define (list->flags list) (make-hash (for/list ([part (group-by car (map (compose (curry map string->symbol) (curryr string-split ":")) list))]) (cons (car (first part)) (map cadr part))))) (define (read-datafile port) (define (parse-string s) (and s (call-with-input-string s read))) (define json (read-json port)) (define (get field) (hash-ref json field)) (report-info (seconds->date (get 'date)) (get 'commit) (get 'branch) (parse-string (get 'seed)) (list->flags (get 'flags)) (get 'points) (get 'iterations) (for/list ([test (get 'tests)] #:when (hash-has-key? test 'vars)) (let ([get (λ (field) (hash-ref test field))]) (define vars (match (hash-ref test 'vars) [(list names ...) (map string->symbol names)] [string-lst (parse-string string-lst)])) (define cost-accuracy (hash-ref test 'cost-accuracy '())) (table-row (get 'name) (parse-string (hash-ref test 'identifier "#f")) (get 'status) (parse-string (hash-ref test 'pre "TRUE")) (parse-string (hash-ref test 'prec "binary64")) (let ([cs (hash-ref test 'conversions "()")]) (if (string? cs) (parse-string cs) (map (curry map parse-string) cs))) vars (hash-ref test 'warnings '()) (parse-string (get 'input)) (parse-string (get 'output)) (or (parse-string (hash-ref test 'spec "#f")) (parse-string (get 'input))) (parse-string (hash-ref test 'target-prog "#f")) (get 'start) (get 'end) (get 'target) (get 'time) (get 'link) cost-accuracy))))) (define (merge-datafiles dfs #:dirs [dirs #f]) (when (null? dfs) (error 'merge-datafiles "Cannot merge no datafiles")) (for ([f (in-list (list report-info-commit report-info-seed report-info-flags report-info-points report-info-iterations))] #:unless (<= (set-count (list->set (map f dfs))) 1)) (error 'merge-datafiles "Cannot merge datafiles at different ~a" f)) (unless dirs (set! dirs (map (const #f) dfs))) (define tests (for/list ([df (in-list dfs)] [dir (in-list dirs)] #:when true [test (in-list (report-info-tests df))]) (struct-copy table-row test (link (if dir (format "~a/~a" dir (table-row-link test)) (table-row-link test)))))) (report-info (last (sort (map report-info-date dfs) < #:key date->seconds)) (report-info-commit (first dfs)) (first (filter values (map report-info-branch dfs))) (report-info-seed (first dfs)) (report-info-flags (first dfs)) (report-info-points (first dfs)) (report-info-iterations (first dfs)) tests)) ================================================ FILE: src/api/demo.rkt ================================================ #lang racket (require json) (require racket/exn) (require openssl/sha1 (rename-in xml [location? xml-location?])) (require web-server/configuration/responders web-server/dispatch web-server/dispatch/extend web-server/dispatchers/dispatch web-server/http/bindings web-server/managers/none web-server/safety-limits web-server/servlet web-server/servlet-env) (require "../config.rkt" "../syntax/types.rkt" "../syntax/read.rkt" "../syntax/sugar.rkt" "../syntax/platform.rkt" "../syntax/load-platform.rkt" "../utils/common.rkt" "../utils/errors.rkt" "../syntax/float.rkt" "../core/points.rkt" "../reports/common.rkt" "../reports/core2mathjs.rkt" "../reports/pages.rkt" "datafile.rkt" "sandbox.rkt" "server.rkt") (provide run-demo) (define *demo?* (make-parameter false)) (define *demo-prefix* (make-parameter "/")) (define *demo-log* (make-parameter false)) (define *demo-output* (make-parameter false)) (define (add-prefix url) (string-replace (string-append (*demo-prefix*) url) "//" "/")) (define-coercion-match-expander hash-arg/m (λ (x) (and (not (and (*demo-output*) ; If we've already saved to disk, skip this job (directory-exists? (build-path (*demo-output*) x)))) (let ([m (regexp-match #rx"^([0-9a-f]+)\\.[0-9a-f.]+" x)]) (and m (job-status (second m)))))) (λ (x) (let ([m (regexp-match #rx"^([0-9a-f]+)\\.[0-9a-f.]+" x)]) (job-status (if m (second m) x))))) (define-bidi-match-expander hash-arg hash-arg/m hash-arg/m) (define-values (dispatch url*) (dispatch-rules [("") main] [("check-status" (string-arg)) check-status] [("timeline" (string-arg)) get-timeline] [("up") check-up] [((hash-arg) (string-arg)) generate-page] [("results.json") generate-report] [("improve") #:method (or "post" "get" "put") improve] [("api" "result" (string-arg)) get-result] [("api" "mathjs") #:method "post" ->mathjs-endpoint] [("api" "translate") #:method "post" translate-endpoint] [("api" "start" "improve") #:method "post" improve-start])) (define (write-results-to-disk result-hash path) (make-directory (build-path (*demo-output*) path)) (for ([page (all-pages result-hash)]) (call-with-output-file (build-path (*demo-output*) path page) (λ (out) (with-handlers ([exn:fail? (page-error-handler result-hash page out)]) (make-page-timeout page out result-hash (*demo-output*) #f #:timeout 10000))))) (define link (path-element->string (last (explode-path path)))) (define data (get-table-data-from-hash result-hash link)) (define data-file (build-path (*demo-output*) "results.json")) (define html-file (build-path (*demo-output*) "index.html")) (define info (if (file-exists? data-file) (let ([info (call-with-input-file data-file read-datafile)]) (struct-copy report-info info [tests (cons data (report-info-tests info))])) (make-report-info (list data) #:seed (get-seed)))) (define tmp-file (build-path (*demo-output*) "results.tmp")) (write-datafile tmp-file info) (rename-file-or-directory tmp-file data-file #t) (copy-file (web-resource "report.html") html-file #t)) (define (generate-page req job-id page) (define path (first (string-split (url->string (request-uri req)) "/"))) (define result-hash (job-results job-id)) (cond [(set-member? (all-pages result-hash) page) ;; Write page contents to disk (when (*demo-output*) (write-results-to-disk result-hash path)) (response 200 #"OK" (current-seconds) #"text" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count))))) (λ (out) (with-handlers ([exn:fail? (page-error-handler result-hash page out)]) (make-page-timeout page out result-hash (*demo-output*) #f #:timeout 1000))))] [else (next-dispatcher)])) (define (generate-report req) (cond [(and (*demo-output*) (file-exists? (build-path (*demo-output*) "results.json"))) (next-dispatcher)] [else (define table-data (for/list ([result (in-list (server-improve-results))]) (get-table-data-from-hash result (hash-ref result 'path)))) (define info (make-report-info table-data #:seed (get-seed))) (response 200 #"OK" (current-seconds) #"text" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count))))) (λ (out) (write-datafile out info)))])) (define url (compose add-prefix url*)) (define (function-list . fn-classes) (define (fn->html fn) `(code ,(~a fn))) (define (fn-class class) (match-define (list fns description ...) class) `((dt () ,@(add-between (map fn->html fns) ", ")) (dd () ,@description))) `(dl ((class "function-list")) ,@(append-map fn-class fn-classes))) (define (herbie-page #:title title #:show-title [title? true] #:scripts [scripts '()] . body) `(html (head (meta ([charset "utf-8"])) (title ,title) ,@(for/list ([script scripts]) `(script ([src ,script] [type "text/javascript"]))) (link ([rel "stylesheet"] [type "text/css"] [href "main.css"]))) (body (header (img ((class "logo") [src "/logo.png"])) ,@(if title? `((h1 ,title)) '())) ,@body))) (define (main req) (when (and (*demo-output*) (not (directory-exists? (*demo-output*)))) (make-directory (*demo-output*))) (response/xexpr #:preamble (list #"") #:headers (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count))))) (herbie-page #:title (if (*demo?*) "Herbie web demo" "Herbie") #:show-title (*demo?*) #:scripts '("//cdnjs.cloudflare.com/ajax/libs/mathjs/1.6.0/math.min.js" "demo.js") `(p "Write a formula below, and Herbie will try to improve it. Enter approximate ranges for inputs.") `(p ([id "options"]) (a ([id "show-example"]) "Show an example") " | " (a ([id "use-fpcore"]) "Use FPCore")) (cond [(server-up?) `(form ([action ,(url improve)] [method "post"] [id "formula"] [data-progress ,(url improve-start)]) (textarea ([name "formula"] [autofocus "true"] [placeholder "e.g. (FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))"])) (input ([name "formula-math"] [placeholder "e.g. sqrt(x + 1) - sqrt(x)"])) (table ([id "input-ranges"])) (ul ([id "errors"])) (ul ([id "warnings"])) (button ([id "run_herbie"] [type "submit"] [tabindex "-1"]) "Improve with Herbie") (pre ([id "progress"] [style "display: none;"])))] [(*demo?*) `(p ([id "crashed"]) "Unfortunately, the online Herbie demo has crashed. The maintainers " "have been notified and will restart Herbie when they've got a moment, " "but if it's been a few days, it can help to " (a ([href "https://github.com/herbie-fp/herbie/issues/new"] [title "File a Herbie issue on Github"]) "file an issue") ".")] [else `(p ([id "crashed"]) "Unfortunately Herbie has crashed. You'll need to restart Herbie to " "continue using it. Please also " (a ([href "https://github.com/herbie-fp/herbie/issues/new"] [title "File a Herbie issue on Github"]) "file a bug report") " with any error messages you find in your terminal.")]) (if (*demo?*) `(p "To handle the high volume of requests, web requests are queued; " "there are " (span ([id "num-jobs"]) ,(~a (server-count))) " jobs in the queue right now. " "Web demo requests may also time out and cap the number of improvement iterations. " "To avoid these limitations, " (a ([href "/doc/latest/installing.html"]) "install Herbie") " on your own computer.") "") `(p ([id "lisp-instructions"]) "Please enter formulas as " (a ([href "https://fpbench.org/spec/fpcore-1.0.html"]) "FPCore") " expressions, including the top-level " (code "FPCore") " form, " "using only the following supported functions:") `(p ([id "mathjs-instructions"] [style "display: none;"]) "Use ordinary mathematical syntax (parsed by " (a ([href "https://mathjs.org"]) "math.js") ")" " and " (a ([href ,(format "https://herbie.uwplse.org/doc/~a/input.html" *herbie-version*)]) "standard functions") " like:") (function-list '((+ - * / abs) "The usual arithmetic functions") '((and or) "Logical connectives (for preconditions)") '((pow) "Raising a value to a power") '((exp log) "Natural exponent and natural log") '((sin cos tan) "The trigonometric functions") '((asin acos atan) "The inverse trigonometric functions") '((sqrt cbrt) "Square and cube roots") '((PI E) "The mathematical constants")) `(p (em "Note") ": " ,@(cond [(not (*demo-output*)) '("formulas submitted here are not logged.")] [(*demo?*) `("all formulas submitted here are logged and made public." (a ([href "./index.html"]) " See what formulas other users submitted."))] [else `("all formulas submitted here are " (a ([href "./index.html"]) "logged") ".")]))))) (define ((post-with-json-response fn) req) (define post-body (request-post-data/raw req)) (define post-data (cond [post-body (bytes->jsexpr post-body)] [#t #f])) (define resp (with-handlers ([exn:fail? (λ (e) (hash 'error (exn->string e)))]) (fn post-data))) (when (hash-has-key? resp 'error) (eprintf "Error handling request: ~a\n" (hash-ref resp 'error))) (if (hash-has-key? resp 'error) (response 500 #"Bad Request" (current-seconds) APPLICATION/JSON-MIME-TYPE (list (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) (λ (op) (write-json resp op))) (response 200 #"OK" (current-seconds) APPLICATION/JSON-MIME-TYPE (filter values (list (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*")) (and (hash-has-key? resp 'job) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 (hash-ref resp 'job)))))) (λ (op) (write-json resp op))))) (define (response/error title body) (response/xexpr #:code 400 #:message #"Bad Request" #:preamble (list #"") (herbie-page #:title title body))) (define (get-result req job-id) (match (job-results job-id) [#f (response 404 #"Job Not Found" (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id)) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) void)] [job-result (response 201 #"Job complete" (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id)) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) (curry write-json job-result))])) (define (improve-common req body go-back) (match (extract-bindings 'formula (request-bindings req)) [(list formula-str) (define formula (with-handlers ([exn:fail? (λ (e) #f)]) (read-syntax 'web (open-input-string formula-str)))) (unless formula (raise-herbie-error "bad input: did you include special characters like `#`?")) (with-handlers ([exn:fail:user:herbie? (λ (e) (response/error "Demo Error" `(div (h1 "Invalid formula") (pre ,(herbie-error->string e)) (p "Formula must be a valid program using only the supported functions. " "Please " (a ([href ,go-back]) "go back") " and try again."))))]) (when (eof-object? formula) (raise-herbie-error "no formula specified")) (define test (parse-test formula)) (define job-id (job-start 'improve test #:seed (get-seed) #:profile? #f #:timeline? #t)) (body job-id))] [_ (response/error "Demo Error" `(p "You didn't specify a formula (or you specified several). " "Please " (a ([href ,go-back]) "go back") " and try again."))])) (define (improve-start req) (improve-common req (λ (job-id) (response/full 201 #"Job started" (current-seconds) #"text/plain" (list (header #"Location" (string->bytes/utf-8 (url check-status job-id))) (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id))) '())) (url main))) (define (check-status req job-id) (match (job-timeline job-id) [(? hash? result-hash) (response/full 201 #"Job complete" (current-seconds) #"text/plain" (list (header #"Location" (string->bytes/utf-8 (add-prefix (format "~a.~a/graph.html" job-id *herbie-commit*)))) (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id)) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) '())] [timeline (response 202 #"Job in progress" (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) (λ (out) (when timeline (for ([entry timeline]) (fprintf out "Doing ~a\n" (hash-ref entry 'type))))))])) (define (check-up req) (response/full (if (server-up?) 200 500) (if (server-up?) #"Up" #"Down") (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) '())) (define (improve req) (improve-common req (λ (job-id) (job-wait job-id) (redirect-to (add-prefix (format "~a.~a/graph.html" job-id *herbie-commit*)) see-other)) (url main))) (define (get-timeline req job-id) (match (job-results job-id) [#f (response 404 #"Job Not Found" (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id)) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) void)] [job-result (response 201 #"Job complete" (current-seconds) #"text/plain" (list (header #"X-Job-Count" (string->bytes/utf-8 (~a (server-count)))) (header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id)) (header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*"))) (curry write-json (hash-ref job-result 'timeline)))])) (define-syntax-rule (define-api-endpoint (name post-data) body ...) (begin (define (function post-data) body ...) (define sync-fn (post-with-json-response (lambda (post-data) (define job-id (function post-data)) (job-wait job-id)))) (define async-fn (post-with-json-response (lambda (post-data) (define job-id (function post-data)) (hasheq 'job job-id 'path (job-path job-id))))) (define-values (new-dispatch new-url) (let ([old-dispatch dispatch]) (dispatch-rules [("api" name) #:method "post" sync-fn] [("api" "start" name) #:method "post" async-fn] [else old-dispatch]))) (set! dispatch new-dispatch))) (define (get-test post-data) (define formula-str (hash-ref post-data 'formula)) (define formula (read-syntax 'web (open-input-string formula-str))) (parse-test formula)) ; The name get-seed is taken. (define (parse-seed post-data) (hash-ref post-data 'seed #f)) (define (json->pcontext json ctx) (define output-repr (context-repr ctx)) (define var-reprs (context-var-reprs ctx)) (define-values (pts exs) (for/lists (pts exs) ([entry (in-list json)]) (match-define (list pt ex) entry) (unless (and (list? pt) (= (length pt) (length var-reprs))) (raise-arguments-error 'json->pcontext "Invalid point" "pt" pt)) (values (list->vector (map json->value pt var-reprs)) (json->value ex output-repr)))) (mk-pcontext pts exs)) (define (get-pcontext post-data) (define test (get-test post-data)) (define sample (hash-ref post-data 'sample)) (json->pcontext sample (test-context test))) (define-api-endpoint ("sample" post-data) (define test (get-test post-data)) (define seed (parse-seed post-data)) (job-start 'sample test #:seed seed)) (define-api-endpoint ("explanations" post-data) (define test (get-test post-data)) (define seed (parse-seed post-data)) (define pcontext (get-pcontext post-data)) (job-start 'explanations test #:seed seed #:pcontext pcontext)) (define-api-endpoint ("analyze" post-data) (define test (get-test post-data)) (define seed (parse-seed post-data)) (define pcontext (get-pcontext post-data)) (job-start 'errors test #:seed seed #:pcontext pcontext)) (define-api-endpoint ("localerror" post-data) (define test (get-test post-data)) (define seed (parse-seed post-data)) (define pcontext (get-pcontext post-data)) (job-start 'local-error test #:seed seed #:pcontext pcontext)) (define-api-endpoint ("alternatives" post-data) (define test (get-test post-data)) (define seed (parse-seed post-data)) (define pcontext (get-pcontext post-data)) (job-start 'alternatives test #:seed seed #:pcontext pcontext)) (define-api-endpoint ("cost" post-data) (job-start 'cost (get-test post-data) #:seed #f)) (define ->mathjs-endpoint (post-with-json-response (lambda (post-data) (define formula (read-syntax 'web (open-input-string (hash-ref post-data 'formula)))) (define result (core->mathjs (syntax->datum formula))) (hasheq 'mathjs result)))) (define (get-converter target-lang) (case target-lang [("python") core->python] [("c") core->c] [("fortran") core->fortran] [("java") core->java] [("julia") core->julia] [("matlab") core->matlab] [("wls") core->wls] [("reflow") core->reflow] [("tex") core->tex] [("js") core->js] [else (error "Unsupported target language:" target-lang)])) (define translate-endpoint (post-with-json-response (lambda (post-data) ; FPCore formula and target language (define formula (read (open-input-string (hash-ref post-data 'formula)))) (define target-lang (hash-ref post-data 'language)) (define converted ((get-converter target-lang) formula "expr")) (hasheq 'result converted 'language target-lang)))) (define (run-demo #:quiet [quiet? #f] #:threads [threads #f] #:browser [browser? #t] #:output output #:demo? demo? #:prefix prefix #:log log #:port port #:public? public) (*demo?* demo?) (*demo-output* output) (*demo-prefix* prefix) (*demo-log* log) (activate-platform! (*platform-name*)) (server-start threads) (serve/servlet dispatch #:listen-ip (if public #f "127.0.0.1") #:port port #:safety-limits (make-safety-limits #:max-request-body-length (* 5 1024 1024)) ; 5 mb body size for det44 bench mark. #:servlet-current-directory (current-directory) #:manager (create-none-manager #f) #:command-line? true #:launch-browser? (and browser? (not quiet?)) #:banner? (not quiet?) #:servlets-root (web-resource) #:server-root-path (web-resource) #:servlet-path "/" #:servlet-regexp #rx"" #:extra-files-paths (filter identity (list (web-resource) (*demo-output*))) #:log-file (*demo-log*) #:file-not-found-responder (gen-file-not-found-responder (web-resource "404.html")))) ================================================ FILE: src/api/run.rkt ================================================ #lang racket (require json math/flonum) (require "../reports/common.rkt" "../reports/pages.rkt" "../reports/timeline.rkt" "../syntax/read.rkt" "../syntax/sugar.rkt" "../syntax/types.rkt" "../syntax/platform.rkt" "../syntax/load-platform.rkt" "../utils/common.rkt" "../utils/profile.rkt" "../utils/timeline.rkt" "../core/points.rkt" "datafile.rkt" "sandbox.rkt" "server.rkt") (provide make-report rerun-report) (define (extract-test row) (define vars (table-row-vars row)) (define repr (get-representation (table-row-precision row))) (define var-reprs (map (curryr cons repr) vars)) (define ctx (context vars repr (map (const repr) vars))) (test (table-row-name row) (table-row-identifier row) (table-row-vars row) (fpcore->prog (table-row-input row) ctx) (fpcore->prog (table-row-output row) ctx) (table-row-target-prog row) (fpcore->prog (table-row-spec row) ctx) (fpcore->prog (table-row-pre row) ctx) (representation-name repr) (for/list ([(k v) (in-dict var-reprs)]) (cons k (representation-name v))) (table-row-conversions row))) (define (make-report bench-dirs #:dir dir #:threads threads) (activate-platform! (*platform-name*)) (define tests (sort (append-map load-tests bench-dirs) string-cijson (apply profile-merge (map json->profile ps)))) (define (generate-bench-report result bench-name test-number dir total-tests) (define report-path (bench-folder-path bench-name test-number)) (define report-directory (build-path dir report-path)) (unless (directory-exists? report-directory) (make-directory report-directory)) (for ([page (all-pages result)]) (call-with-output-file (build-path report-directory page) #:exists 'replace (λ (out) (with-handlers ([exn:fail? (page-error-handler result page out)]) (make-page-timeout page out result #t #f #:timeout 10000))))) (get-table-data-from-hash result report-path)) (define (run-tests tests #:dir dir #:threads threads) (define seed (get-seed)) (unless (directory-exists? dir) (make-directory dir)) (server-start threads) (define job-ids (for/list ([test (in-list tests)]) (job-start 'improve test #:seed seed #:pcontext #f #:profile? #t #:timeline? #t))) (define total-tests (length tests)) (define results (for/list ([job-id (in-list job-ids)] [test (in-list tests)] [test-number (in-naturals)]) (define result (job-wait job-id)) (print-test-result (+ test-number 1) total-tests test result) (begin0 (generate-bench-report result (test-name test) test-number dir total-tests) (job-forget job-id)))) (define info (make-report-info results #:seed seed)) (write-datafile (build-path dir "results.json") info) (copy-file (web-resource "report-page.js") (build-path dir "report-page.js") #t) (copy-file (web-resource "report.js") (build-path dir "report.js") #t) (copy-file (web-resource "report.css") (build-path dir "report.css") #t) (copy-file (web-resource "logo-car.png") (build-path dir "logo-car.png") #t) (copy-file (web-resource "report.html") (build-path dir "index.html") #t) (define timeline (apply timeline-merge (read-json-files info dir "timeline.json"))) (call-with-output-file (build-path dir "timeline.json") (curry write-json timeline) #:exists 'replace) (define profile (merge-profile-jsons (read-json-files info dir "profile.json"))) (call-with-output-file (build-path dir "profile.json") (curry write-json profile) #:exists 'replace) (call-with-output-file (build-path dir "timeline.html") #:exists 'replace (λ (out) (write-html (make-timeline "Herbie run" timeline #:info info #:path ".") out))) ; Delete old files (define expected-dirs (map string->path (filter identity (map table-row-link (report-info-tests info))))) (define actual-dirs (filter (λ (name) (directory-exists? (build-path dir name))) (directory-list dir))) (define extra-dirs (filter (λ (name) (not (member name expected-dirs))) actual-dirs)) (for ([subdir extra-dirs]) (with-handlers ([exn:fail:filesystem? (const true)]) (delete-directory/files (build-path dir subdir))))) ;; Generate a path for a given benchmark name (define (bench-folder-path bench-name index) (define replaced (string-replace bench-name #px"\\W+" "")) (format "~a-~a" index (substring replaced 0 (min (string-length replaced) 50)))) (define (hash-ref-path hash . path) (match path ['() hash] [(cons (? symbol? key) rest) (apply hash-ref-path (hash-ref hash key) rest)] [(cons (? integer? key) rest) (apply hash-ref-path (list-ref hash key) rest)])) (define (print-test-result i n test result-hash) (eprintf "~a/~a\t" (~a i #:min-width 3 #:align 'right) n) (define name (test-name test)) (match (hash-ref-path result-hash 'status) ["failure" (match-define (list 'exn type msg url locs tb) (hash-ref-path result-hash 'backend)) (if type (eprintf "[ ERROR ]\t\t~a\n" name) (eprintf "[ CRASH ]\t\t~a\n" name))] ["timeout" (eprintf "[TIMEOUT]\t\t~a\n" name)] [_ (define bits (representation-total-bits (test-output-repr test))) (define time (hash-ref-path result-hash 'time)) (define start-score (errors-score (list->flvector (hash-ref-path result-hash 'backend 'start 'errors)))) (define end-score (errors-score (list->flvector (hash-ref-path result-hash 'backend 'end 0 'errors)))) (eprintf "[~as] ~a% → ~a%\t~a\n" (~r (/ time 1000) #:min-width 6 #:precision '(= 1)) (~r (* 100 (- 1 (/ start-score bits))) #:min-width 3 #:precision 0) (~r (* 100 (- 1 (/ end-score bits))) #:min-width 3 #:precision 0) name)])) ================================================ FILE: src/api/sandbox.rkt ================================================ #lang racket (require racket/engine math/flonum json) (require "../syntax/read.rkt" "../syntax/syntax.rkt" "../syntax/sugar.rkt" "../syntax/types.rkt" "../syntax/load-platform.rkt" "../syntax/batch.rkt" "../core/localize.rkt" "../core/alternative.rkt" "../core/compiler.rkt" "../utils/common.rkt" "datafile.rkt" "../utils/errors.rkt" "../syntax/float.rkt" "../core/sampling.rkt" "../core/mainloop.rkt" "../syntax/platform.rkt" "../core/programs.rkt" "../core/points.rkt" "../core/explain.rkt" "../utils/profile.rkt" "../utils/timeline.rkt" (submod "../utils/timeline.rkt" debug)) (provide run-herbie get-table-data-from-hash *reeval-pts* (struct-out job-result) (struct-out improve-result) (struct-out alt-analysis)) (struct job-result (command test status time timeline profile warnings backend)) (struct improve-result (pcontext start target end)) (struct alt-analysis (alt errors) #:prefab) ;; API users can supply their own, weird set of points, in which case ;; the first 256 are training points and everything is test points. ;; For backwards compatibility, exactly 8256 points are split as ;; Herbie expects (first 256 training, rest are test). (define (partition-pcontext joint-pcontext) (define num-points (pcontext-length joint-pcontext)) (cond [(= num-points (+ (*num-points*) (*reeval-pts*))) (split-pcontext joint-pcontext (*num-points*) (*reeval-pts*))] [else ; the training set will just be up to the first (*num-points*) ; the testing set will just be the entire set (define training-count (min (*num-points*) num-points)) (define testing-count (- num-points training-count)) (define-values (train-pcontext _) (split-pcontext joint-pcontext training-count testing-count)) (values train-pcontext joint-pcontext)])) ;; API Functions ;; The main Herbie function (define (get-alternatives test joint-pcontext) (unless joint-pcontext (error 'get-alternatives "cannnot run without a pcontext")) (define-values (train-pcontext test-pcontext) (partition-pcontext joint-pcontext)) (define alternatives (run-improve! (test-input test) (test-spec test) (*context*) train-pcontext)) ;; compute error/cost for input expression (define start-expr (test-input test)) (define start-alt (make-alt start-expr)) (define start-errs (errors start-expr test-pcontext (*context*))) (define start-alt-data (alt-analysis start-alt start-errs)) ;; optionally compute error/cost for input expression (define target-alt-data ;; When in platform, evaluate error (for/list ([(expr is-valid?) (in-dict (test-output test))] #:when is-valid?) (define target-expr (fpcore->prog expr (*context*))) (define target-errs (errors target-expr test-pcontext (*context*))) (alt-analysis (make-alt target-expr) target-errs))) ;; compute error/cost for output expression ;; and sort alternatives by accuracy + cost on testing subset (define test-errs (exprs-errors (map alt-expr alternatives) test-pcontext (*context*))) (define sorted-end-exprs (sort-alts alternatives test-errs)) (define end-exprs (map (compose alt-expr car) sorted-end-exprs)) (define end-errs (map cdr sorted-end-exprs)) (define end-data (map alt-analysis alternatives end-errs)) (improve-result test-pcontext start-alt-data target-alt-data end-data)) (define (get-cost test) (define cost-proc (platform-cost-proc (*active-platform*))) (define output-repr (context-repr (*context*))) (cost-proc (test-input test) output-repr)) (define (get-errors test pcontext) (unless pcontext (error 'get-errors "cannnot run without a pcontext")) (define-values (_ test-pcontext) (partition-pcontext pcontext)) (define errs (errors (test-input test) test-pcontext (*context*))) (for/list ([(pt _) (in-pcontext test-pcontext)] [err (in-flvector errs)]) (cons pt err))) (define (get-explanations test pcontext) (unless pcontext (error 'explain "cannot run without a pcontext")) (define-values (fperrors sorted-explanations-table confusion-matrix maybe-confusion-matrix total-confusion-matrix freqs) (explain (test-input test) (*context*) pcontext)) sorted-explanations-table) ;; Given a test and a sample of points, computes the local error at every node in the expression ;; returning a tree of errors that mirrors the structure of the expression. ;; If the sample contains the expected number of points, i.e., `(*num-points*) + (*reeval-pts*)`, ;; then the first `*num-points*` will be discarded and the rest will be used for evaluation, ;; otherwise the entire set is used. (define (get-local-error test pcontext) (unless pcontext (error 'get-local-error "cannnot run without a pcontext")) (local-error-as-tree (test-input test) (*context*) pcontext)) (define (get-sample test) (random) ;; Tick the random number generator, for backwards compatibility (define specification (prog->spec (or (test-spec test) (test-input test)))) (define precondition (prog->spec (test-pre test))) (define-values (batch brfs) (progs->batch (list specification))) (define sample (parameterize ([*num-points* (+ (*num-points*) (*reeval-pts*))]) (sample-points precondition batch brfs (list (*context*))))) (apply mk-pcontext sample)) ;; ;; Public interface ;; (define (run-herbie command test #:seed [seed #f] #:pcontext [pcontext #f] #:profile? [profile? #f] #:timeline? [timeline? #f]) (define timeline #f) (define profile #f) (define (on-exception start-time e) (parameterize ([*timeline-disabled* (not timeline?)]) (timeline-event! 'end) (define time (- (current-inexact-milliseconds) start-time)) (match command ['improve (job-result command test 'failure time (timeline-extract) #f (warning-log) e)] [_ (raise e)]))) (define (on-timeout) (parameterize ([*timeline-disabled* (not timeline?)]) (timeline-load! timeline) (timeline-event! 'end) (match command ['improve (job-result command test 'timeout (*timeout*) (timeline-extract) #f (warning-log) #f)] [_ (raise-arguments-error 'run-herbie "command timed out" "command" command)]))) (define (compute-result) (parameterize ([*timeline-disabled* (not timeline?)]) (define start-time (current-inexact-milliseconds)) (reset!) (*context* (test-context test)) (activate-platform! (*platform-name*)) (set! timeline (*timeline*)) (when seed (set-seed! seed)) (with-handlers ([exn? (curry on-exception start-time)]) (timeline-event! 'start) ; Prevents the timeline from being empty. (define result (match command ['alternatives (get-alternatives test pcontext)] ['cost (get-cost test)] ['errors (get-errors test pcontext)] ['explanations (get-explanations test pcontext)] ['improve (get-alternatives test (get-sample test))] ['local-error (get-local-error test pcontext)] ['sample (get-sample test)] [_ (raise-arguments-error 'compute-result "unknown command" "command" command)])) (timeline-event! 'end) (define time (- (current-inexact-milliseconds) start-time)) (job-result command test 'success time (timeline-extract) #f (warning-log) result)))) (define (in-engine _) (cond [profile? (define result (profile-thunk compute-result (λ (p) (set! profile (profile->json p))))) (struct-copy job-result result [profile profile])] [else (compute-result)])) (define run-custodian (make-custodian)) (begin0 (parameterize ([current-custodian run-custodian]) (define eng (engine in-engine)) (if (engine-run (*timeout*) eng) (engine-result eng) (on-timeout))) (custodian-shutdown-all run-custodian))) (define (dummy-table-row-from-hash result-hash status link) (define test (car (load-tests (open-input-string (hash-ref result-hash 'test))))) (define repr (test-output-repr test)) (table-row (test-name test) (test-identifier test) status (prog->fpcore (test-pre test) (test-context test)) (representation-name repr) '() ; TODO: eliminate field (test-vars test) (map car (hash-ref result-hash 'warnings)) (prog->fpcore (test-input test) (test-context test)) #f (prog->fpcore (test-spec test) (test-context test)) (test-output test) #f #f #f (hash-ref result-hash 'time) link '())) (define (get-table-data-from-hash result-hash link) (define test (car (load-tests (open-input-string (hash-ref result-hash 'test))))) (define backend (hash-ref result-hash 'backend)) (define status (hash-ref result-hash 'status)) (match status ["success" (define start (hash-ref backend 'start)) (define targets (hash-ref backend 'target)) (define end (hash-ref backend 'end)) (define expr-cost (platform-cost-proc (*active-platform*))) (define repr (test-output-repr test)) ; starting expr analysis (define start-expr (read (open-input-string (hash-ref start 'expr)))) (define start-score (errors-score (list->flvector (hash-ref start 'errors)))) (define start-cost (hash-ref start 'cost)) (define target-cost-score (for/list ([target targets]) (define target-expr (read (open-input-string (hash-ref target 'expr)))) (define tar-cost (hash-ref target 'cost)) (define tar-score (errors-score (list->flvector (hash-ref target 'errors)))) (list tar-cost tar-score))) ; Important to calculate value of status (define best-score (if (null? target-cost-score) target-cost-score (apply min (map second target-cost-score)))) (define end-exprs (for/list ([end-analysis (in-list end)]) (read (open-input-string (hash-ref end-analysis 'expr))))) (define end-scores (for/list ([end-analysis (in-list end)]) (errors-score (list->flvector (hash-ref end-analysis 'errors))))) (define end-costs (map (curryr hash-ref 'cost) end)) ; terribly formatted pareto-optimal frontier (define (round3 x) (/ (round (* x 1000)) 1000.0)) (define cost&accuracy (list (list (round3 start-cost) (round3 start-score)) (list (round3 (car end-costs)) (round3 (car end-scores))) (map (λ (c s) (list (round3 c) (round3 s))) (cdr end-costs) (cdr end-scores)))) (define fuzz 0.1) (define end-score (car end-scores)) (define status (cond [(not (null? best-score)) (cond [(< end-score (- best-score fuzz)) "gt-target"] [(< end-score (+ best-score fuzz)) "eq-target"] [(> end-score (+ start-score fuzz)) "lt-start"] [(> end-score (- start-score fuzz)) "eq-start"] [(> end-score (+ best-score fuzz)) "lt-target"])] [(and (< start-score 1) (< end-score (+ start-score 1))) "ex-start"] [(< end-score (- start-score 1)) "imp-start"] [(< end-score (+ start-score fuzz)) "apx-start"] [else "uni-start"])) (struct-copy table-row (dummy-table-row-from-hash result-hash status link) [start start-score] [target target-cost-score] [result end-score] [output (car end-exprs)] [cost-accuracy cost&accuracy])] ["failure" (match-define (list 'exn type _ ...) backend) (define status (if type "error" "crash")) (dummy-table-row-from-hash result-hash status link)] ["timeout" (dummy-table-row-from-hash result-hash "timeout" link)])) ================================================ FILE: src/api/server.rkt ================================================ #lang racket (require openssl/sha1) (require (only-in xml write-xexpr)) (require json) (require data/queue) (require math/flonum) (require "../syntax/read.rkt" "../syntax/sugar.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "../syntax/platform.rkt" "../syntax/load-platform.rkt" "../core/alternative.rkt" "../utils/common.rkt" "../utils/errors.rkt" "../syntax/float.rkt" "../core/points.rkt" "../reports/common.rkt" "../reports/history.rkt" "../reports/pages.rkt" "../reports/plot.rkt" "../config.rkt" "datafile.rkt" "sandbox.rkt" (submod "../utils/timeline.rkt" debug)) (provide job-path job-start job-status job-forget job-wait job-results job-timeline server-start server-improve-results server-count server-up?) (define (log msg . args) (when false (apply eprintf msg args))) ;; Tracing support (define (current-timestamp) (exact-floor (* 1000 (current-inexact-milliseconds)))) (define *gc-logger* #f) (define (trace-start) (when (flag-set? 'dump 'trace) (set! *gc-logger* (make-log-receiver (current-logger) 'debug 'GC)) (call-with-output-file "dump-trace.json" #:exists 'truncate (λ (out) (fprintf out "{\"traceEvents\":[") (write-json (hash 'name "process_name" 'ph "M" 'ts 0 'pid 0 'tid 0 'args (hash 'name "herbie")) out) (fprintf out ",") (write-json (hash 'name "thread_name" 'ph "M" 'ts 0 'pid 0 'tid (equal-hash-code 'gc) 'args (hash 'name "GC")) out))))) (define (trace-sync) (when *gc-logger* (let drain () (match (sync/timeout 0 *gc-logger*) [#f (void)] [(vector _lvl _msg (app struct->vector data) _topic) (define mode (~a (vector-ref data 1))) (define start (exact-floor (* 1000 (vector-ref data 9)))) (define end (exact-floor (* 1000 (vector-ref data 10)))) (define post-amount (vector-ref data 5)) (define post-admin-amount (vector-ref data 6)) (call-with-output-file "dump-trace.json" #:exists 'append (λ (out) (fprintf out ",") (write-json (hash 'name mode 'ph "X" 'ts start 'dur (- end start) 'pid 0 'tid (equal-hash-code 'gc) 'args (hash)) out) (fprintf out ",") (write-json (hash 'name "Memory" 'ph "C" 'ts end 'pid 0 'tid (equal-hash-code 'gc) 'args (hash 'live post-amount 'admin post-admin-amount)) out))) (drain)])))) (define (trace worker-id name phase [args (hash)]) (when (flag-set? 'dump 'trace) (trace-sync) (call-with-output-file "dump-trace.json" #:exists 'append (λ (out) (fprintf out ",") (write-json (hash 'name (~a name) 'ph (~a phase) 'ts (current-timestamp) 'pid 0 'tid worker-id 'args args) out))))) (define (trace-end) (when (flag-set? 'dump 'trace) (trace-sync) (call-with-output-file "dump-trace.json" #:exists 'append (λ (out) (fprintf out "]}\n"))))) (define old-exit (exit-handler)) (exit-handler (λ (v) (trace-end) (old-exit v))) ;; Job-specific public API (define (job-path id) (format "~a.~a" id *herbie-commit*)) (define (job-start command test #:seed [seed #f] #:pcontext [pcontext #f] #:profile? [profile? #f] #:timeline? [timeline? #f]) (define job-id (manager-ask 'start manager command test seed pcontext profile? timeline?)) (log "Job ~a, Qed up for program: ~a\n" job-id (test-name test)) job-id) (define (job-status job-id) (log "Checking on: ~a.\n" job-id) (manager-ask 'check job-id)) (define (job-forget job-id) (log "Forgetting job: ~a.\n" job-id) (manager-ask 'forget job-id)) (define (job-wait job-id) (define finished-result (manager-ask 'wait manager job-id)) (log "Done waiting for: ~a\n" job-id) finished-result) (define (job-results job-id) (log "Getting result for job: ~a.\n" job-id) (manager-ask 'result job-id)) (define (job-timeline job-id) (log "Getting timeline for job: ~a.\n" job-id) (manager-ask 'timeline job-id)) ;; Whole-server public methods (define (server-start threads) (trace-start) (cond [threads (eprintf "Starting Herbie ~a with ~a workers and seed ~a...\n" *herbie-version* threads (get-seed))] [else (eprintf "Starting Herbie ~a with seed ~a...\n" *herbie-version* (get-seed))]) (set! manager (if threads (make-manager threads) 'basic))) (define (server-improve-results) (log "Getting improve results.\n") (manager-ask 'improve)) (define (server-count) (manager-ask 'count)) (define (server-up?) (match manager [(? place? x) (not (sync/timeout 0 (place-dead-evt x)))] ['basic #t])) ;; Interface for two manager types (threaded and basic) (define manager #f) (define (manager-ask msg . args) (log "Asking manager: ~a.\n" msg) (trace-sync) (match manager [(? place? x) (apply manager-ask-threaded x msg args)] ['basic (apply manager-ask-basic msg args)])) (define (manager-ask-threaded manager msg . args) (define-values (a b) (place-channel)) (place-channel-put manager (list* msg b args)) (place-channel-get a)) (define (manager-ask-basic msg . args) (match (list* msg args) ; public commands [(list 'start 'basic command test seed pcontext profile? timeline?) (define job (herbie-command command test seed pcontext profile? timeline?)) (define job-id (compute-job-id job)) (hash-set! queued-jobs job-id job) job-id] [(list 'wait 'basic job-id) (define command (hash-ref queued-jobs job-id #f)) (define result (and command (herbie-do-server-job 0 command job-id))) (when command (hash-remove! queued-jobs job-id) (hash-set! completed-jobs job-id result)) result] [(list 'result job-id) (hash-ref completed-jobs job-id #f)] [(list 'timeline job-id) (define command (hash-ref queued-jobs job-id #f)) (define result (and command (herbie-do-server-job 0 command job-id))) (when command (hash-remove! queued-jobs job-id) (hash-set! completed-jobs job-id result)) result] [(list 'check job-id) (define command (hash-ref queued-jobs job-id #f)) (define result (and command (herbie-do-server-job 0 command job-id))) (when command (hash-remove! queued-jobs job-id) (hash-set! completed-jobs job-id result)) job-id] [(list 'count) (hash-count queued-jobs)] [(list 'improve) (for/list ([result (in-hash-values completed-jobs)] #:when (equal? (hash-ref result 'command) "improve")) result)] [(list 'forget job-id) (hash-remove! completed-jobs job-id) (void)])) ;; Implementation of threaded manager (struct herbie-command (command test seed pcontext profile? timeline?) #:prefab) (define queued-jobs (make-hash)) (define completed-jobs (make-hash)) (define (compute-job-id job-info) (sha1 (open-input-string (~s job-info)))) (define-syntax (place/context* stx) (syntax-case stx () [(_ name #:parameters (params ...) body ...) (with-syntax ([(fresh ...) (generate-temporaries #'(params ...))]) #'(let ([fresh (params)] ...) (place/context name (parameterize ([params fresh] ...) body ...))))])) (define (make-manager worker-count) (place/context* ch #:parameters (*flags* *num-iterations* *num-points* *timeout* *reeval-pts* *node-limit* *max-find-range-depth* *platform-name* *functions*) (activate-platform! (*platform-name*)) ; not sure if the above code is actaully needed. (define busy-workers (make-hash)) (define waiting-workers (make-hash)) (define current-jobs (make-hash)) (define queued-job-ids (make-queue)) (when (eq? worker-count #t) (set! worker-count (processor-count))) (for ([i (in-range worker-count)]) (hash-set! waiting-workers i (make-worker i))) (define dead-worker-evts (map place-dead-evt (hash-values waiting-workers))) (log "~a workers ready.\n" (hash-count waiting-workers)) (define waiting (make-hash)) (log "Manager waiting to assign work.\n") (for ([i (in-naturals)]) (match (apply sync ch dead-worker-evts) [(? evt?) (error 'make-manager "worker place died")] ;; Private API [(list 'assign self) (define reassigned '()) (for ([(wid worker) (in-hash waiting-workers)] #:when (not (queue-empty? queued-job-ids))) (define jid (dequeue! queued-job-ids)) (define command (hash-ref queued-jobs jid)) (log "Starting worker [~a] on [~a].\n" jid (test-name (herbie-command-test command))) (place-channel-put worker (list 'apply self command jid)) (hash-set! current-jobs jid wid) (hash-set! busy-workers wid worker) (set! reassigned (cons wid reassigned)) (hash-remove! queued-jobs jid)) (for ([wid reassigned]) (hash-remove! waiting-workers wid))] ; Job is finished save work and free worker. Move work to 'send state. [(list 'finished self wid job-id result) (log "Job ~a finished, saving result.\n" job-id) (hash-set! completed-jobs job-id result) ; move worker to waiting list (hash-remove! current-jobs job-id) (hash-set! waiting-workers wid (hash-ref busy-workers wid)) (hash-remove! busy-workers wid) (log "waiting job ~a completed\n" job-id) (place-channel-put self (list 'send job-id result)) (place-channel-put self (list 'assign self))] [(list 'send job-id result) (log "Sending result for ~a.\n" job-id) (for ([handle (hash-ref waiting job-id '())]) (place-channel-put handle result)) (hash-remove! waiting job-id)] ;; Public API [(list 'start handler self command test seed pcontext profile? timeline?) (define job (herbie-command command test seed pcontext profile? timeline?)) (define job-id (compute-job-id job)) (place-channel-put handler job-id) ; Check if the work has been completed already if not assign the work. (cond [(hash-has-key? completed-jobs job-id) (place-channel-put self (list 'send job-id (hash-ref completed-jobs job-id)))] [else (hash-set! queued-jobs job-id job) (enqueue! queued-job-ids job-id) (place-channel-put self (list 'assign self))])] [(list 'wait handler self job-id) (log "Waiting for job: ~a\n" job-id) ; first we add the handler to the wait list. (hash-update! waiting job-id (curry append (list handler)) '()) (define result (hash-ref completed-jobs job-id #f)) ; check if the job is completed or not. (unless (false? result) (log "Done waiting for job: ~a\n" job-id) ; we have a result to send. (place-channel-put self (list 'send job-id result)))] ; Get the result for the given id, return false if no work found. [(list 'result handler job-id) (place-channel-put handler (hash-ref completed-jobs job-id #f))] [(list 'timeline handler job-id) (define wid (hash-ref current-jobs job-id #f)) (cond [wid (log "Worker[~a] working on ~a.\n" wid job-id) (define-values (a b) (place-channel)) (place-channel-put (hash-ref busy-workers wid) (list 'timeline b)) (define requested-timeline (place-channel-get a)) (place-channel-put handler requested-timeline)] [else (log "Job complete, no timeline, send result.\n") (place-channel-put handler (hash-ref completed-jobs job-id #f))])] [(list 'check handler job-id) (place-channel-put handler (and (hash-has-key? completed-jobs job-id) job-id))] ; Returns the current count of working workers. [(list 'count handler) (define total (+ (hash-count busy-workers) (hash-count queued-jobs))) (log "Currently ~a jobs total.\n" total) (place-channel-put handler total)] ; Retrieve the improve results for results.json [(list 'improve handler) (define improved-list (for/list ([result (in-hash-values completed-jobs)] #:when (equal? (hash-ref result 'command) "improve")) result)) (place-channel-put handler improved-list)] ; Forget a completed job result [(list 'forget handler job-id) (hash-remove! completed-jobs job-id) (place-channel-put handler (void))])))) ;; Implementation of threaded worker (define (warn-single-threaded-mpfr) (local-require ffi/unsafe) (local-require math/private/bigfloat/mpfr) (unless ((get-ffi-obj 'mpfr_buildopt_tls_p mpfr-lib (_fun -> _bool))) (warn 'mpfr-threads "Your MPFR is single-threaded. Herbie will work but be slower than normal."))) (define (make-worker worker-id) (warn-single-threaded-mpfr) (place/context* ch #:parameters (*flags* *num-iterations* *num-points* *timeout* *reeval-pts* *node-limit* *max-find-range-depth* *platform-name* *functions*) (activate-platform! (*platform-name*)) (define worker-thread (thread (λ () (let loop () (match-define (list manager worker-id job-id command) (thread-receive)) (log "run-job: ~a, ~a\n" worker-id job-id) (define out-result (herbie-do-server-job worker-id command job-id)) (log "Job: ~a finished, returning work to manager\n" job-id) (place-channel-put manager (list 'finished manager worker-id job-id out-result)) (loop))))) (define timeline #f) (define current-job-id #f) (for ([_ (in-naturals)]) (match (sync ch (thread-dead-evt worker-thread)) [(? evt?) (error 'make-worker "worker thread died")] [(list 'apply manager command job-id) (set! timeline (*timeline*)) (set! current-job-id job-id) (log "[~a] working on [~a].\n" job-id (test-name (herbie-command-test command))) (thread-send worker-thread (list manager worker-id job-id command))] [(list 'timeline handler) (log "Timeline requested from worker[~a] for job ~a\n" worker-id current-job-id) (place-channel-put handler (reverse (unbox timeline)))])))) (define (herbie-do-server-job worker-id h-command job-id) (match-define (herbie-command command test seed pcontext profile? timeline?) h-command) (define metadata (hash 'job-id job-id 'command (~a command) 'name (test-name test))) (trace worker-id 'herbie 'B metadata) (define herbie-result (run-herbie command test #:seed seed #:pcontext pcontext #:profile? profile? #:timeline? timeline?)) (trace worker-id 'herbie 'E metadata) (trace worker-id 'to-json 'B metadata) (define basic-output ((get-json-converter command) herbie-result job-id)) (trace worker-id 'to-json 'E metadata) ;; Add default fields that all commands have (hash-set* basic-output 'job job-id 'path (job-path job-id) 'command (~a command) 'name (test-name test) 'status (~a (job-result-status herbie-result)) 'time (job-result-time herbie-result) 'warnings (job-result-warnings herbie-result))) ;; JSON conversion stuff (define (get-json-converter command) (match command ['alternatives make-alternatives-result] ['cost make-cost-result] ['errors make-error-result] ['explanations make-explanation-result] ['improve make-alternatives-result] ['local-error make-local-error-result] ['sample make-sample-result] [_ (error 'compute-result "unknown command ~a" command)])) (define (make-explanation-result herbie-result job-id) (hasheq 'explanation (job-result-backend herbie-result))) (define (make-local-error-result herbie-result job-id) (hasheq 'tree (job-result-backend herbie-result))) (define (make-sample-result herbie-result job-id) (define test (job-result-test herbie-result)) (define pctx (job-result-backend herbie-result)) (define ctx (test-context test)) (hasheq 'points (pcontext->json pctx ctx))) (define (make-cost-result herbie-result job-id) (hasheq 'cost (job-result-backend herbie-result))) (define (make-error-result herbie-result job-id) (define test (job-result-test herbie-result)) (define errs (for/list ([(pt err) (in-dict (job-result-backend herbie-result))]) (list (for/list ([val (in-vector pt)] [repr (in-list (context-var-reprs (test-context test)))]) (value->json val repr)) (format-bits err)))) (hasheq 'points errs)) (define (make-alternatives-result herbie-result job-id) (define test (job-result-test herbie-result)) (define backend (job-result-backend herbie-result)) (define timeline (job-result-timeline herbie-result)) (define profile (job-result-profile herbie-result)) (define ctx (test-context test)) (define-values (altns pcontext) (cond [(equal? (job-result-status herbie-result) 'success) (define altns (map alt-analysis-alt (improve-result-end backend))) (define pcontext (improve-result-pcontext backend)) (values altns pcontext)] [else (values '() #f)])) (define errcache (cond [(equal? (job-result-status herbie-result) 'success) (define all-alts (map alt-analysis-alt (append (list (improve-result-start backend)) (improve-result-target backend) (improve-result-end backend)))) (define exprs (remove-duplicates (append-map collect-expressions all-alts))) (make-hash (map cons exprs (exprs-errors exprs pcontext ctx)))] [else #f])) (define test-fpcore (alt->fpcore test (make-alt (test-input test)))) (define fpcores (if (equal? (job-result-status herbie-result) 'success) (for/list ([altn (in-list altns)]) (~s (alt->fpcore test altn))) (list (~s test-fpcore)))) (define backend-hash (match (job-result-status herbie-result) ['success (backend-improve-result-hash-table backend test errcache)] ['timeout #f] ['failure (exception->datum backend)])) (define derivations (for/list ([altn (in-list altns)] [analysis (if (hash? backend-hash) (hash-ref backend-hash 'end) '())]) (hash-ref analysis 'history))) (hasheq 'test (~s test-fpcore) 'timeline timeline 'profile profile 'alternatives ; FIXME: currently used by Odyssey but should maybe be in 'backend? fpcores 'derivations derivations 'backend backend-hash)) (define (backend-improve-result-hash-table backend test errcache) (define ctx (test-context test)) (define pcontext (improve-result-pcontext backend)) (hasheq 'pcontext (pcontext->json pcontext ctx) 'start (analysis->json (improve-result-start backend) pcontext test errcache) 'target (map (curryr analysis->json pcontext test errcache) (improve-result-target backend)) 'end (map (curryr analysis->json pcontext test errcache) (improve-result-end backend)))) (define (pcontext->json pcontext ctx) (define var-reprs (context-var-reprs ctx)) (define out-repr (context-repr ctx)) (for/list ([(pt ex) (in-pcontext pcontext)]) (list (for/list ([val (in-vector pt)] [repr (in-list var-reprs)]) (value->json val repr)) (value->json ex out-repr)))) (define (analysis->json analysis pcontext test errcache) (define repr (context-repr (test-context test))) (match-define (alt-analysis alt test-errors) analysis) (define cost (alt-cost alt repr)) (define history-json (render-json alt pcontext (test-context test) errcache)) (define vars (test-vars test)) (define splitpoints (for/list ([var (in-list vars)]) (if (equal? var (regime-var alt)) (for/list ([val (in-list (regime-splitpoints alt))]) (real->ordinal (repr->real val repr) repr)) '()))) (hasheq 'expr (~s (alt-expr alt)) 'history history-json 'errors (flvector->list test-errors) 'cost cost 'splitpoints splitpoints)) (define (alt->fpcore test altn) (define out-repr (test-output-repr test)) (define out-base-repr (array-representation-base out-repr)) `(FPCore ,@(filter identity (list (test-identifier test))) ,(for/list ([var (in-list (test-vars test))] [repr (in-list (test-var-reprs test))]) (cond [(array-representation? repr) (define dims (array-representation-shape repr)) (define elem-repr (array-representation-base repr)) (if (equal? elem-repr out-base-repr) (append (list var) dims) (append (list '! ':precision (representation-name elem-repr) var) dims))] [else (if (equal? repr out-base-repr) var (list '! ':precision (representation-name repr) var))])) :name ,(test-name test) :precision ,(representation-name out-base-repr) ,@(if (equal? (test-pre test) '(TRUE)) '() `(:pre ,(prog->fpcore (test-pre test) (test-context test)))) ,@(if (equal? (test-spec test) empty) '() `(:spec ,(prog->fpcore (test-spec test) (test-context test)))) ,@(if (equal? (test-expected test) #t) '() `(:herbie-expected ,(test-expected test))) ,@(apply append (for/list ([(target enabled?) (in-dict (test-output test))] #:when enabled?) `(:alt ,target))) ,(prog->fpcore (alt-expr altn) (test-context test)))) ================================================ FILE: src/api/shell.rkt ================================================ #lang racket (require "../syntax/platform.rkt" "../syntax/load-platform.rkt" "../syntax/read.rkt" "../utils/common.rkt" "server.rkt") (provide run-shell run-improve) (define (get-shell-input) (printf "herbie> ") (with-handlers ([(or/c exn:fail:user? exn:fail:read?) (λ (e) ((error-display-handler) (exn-message e) e) (get-shell-input))]) (define input (parameterize ([read-decimal-as-inexact false]) (read-syntax "stdin" (current-input-port)))) (cond [(eof-object? input) (printf "\n") eof] [else (parse-test input)]))) (define (job-result->fpcore result) (read (open-input-string (first (hash-ref result 'alternatives))))) (define (print-improve-outputs tests results p #:seed [seed #f]) (when seed (fprintf p ";; seed: ~a\n\n" seed)) (for ([res results] [test tests] #:when res) (define name (hash-ref res 'name)) (match (hash-ref res 'status) ["failure" (match-define (list 'exn type msg url locs traceback) (hash-ref res 'backend)) (fprintf p ";; ~a in ~a\n" (if type "Error" "Crash") name)] ["timeout" (fprintf p ";; ~a times out in ~as\n" (/ (*timeout*) 1000) name)] ["success" (void)]) (displayln (fpcore->string (job-result->fpcore res)) p) (newline))) (define (run-improve input output #:threads [threads #f]) (define seed (get-seed)) (activate-platform! (*platform-name*)) (define tests (load-tests input)) (server-start threads) (define ids (for/list ([test (in-list tests)]) (job-start 'improve test #:seed seed #:pcontext #f #:profile? #f #:timeline? #f))) (define results (for/list ([id ids]) (job-wait id))) (if (equal? output "-") (print-improve-outputs tests results (current-output-port) #:seed seed) (call-with-output-file output #:exists 'replace (λ (p) (print-improve-outputs tests results p #:seed seed))))) (define (run-shell) (define seed (get-seed)) (activate-platform! (*platform-name*)) (server-start #f) (eprintf "Find help on https://herbie.uwplse.org/, 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-shell-input eof-object?)] [idx (in-naturals)]) (define result (job-wait (job-start 'improve test #:seed seed))) (match (hash-ref result 'status) ["success" (displayln (fpcore->string (job-result->fpcore result)))] ["failure" (match-define (list 'exn type msg url locs traceback) (hash-ref result 'backend)) (printf "; ~a\n" msg) (for ([loc (in-list locs)]) (match-define (list msg file line col pos) loc) (printf "; ~a:~a~a: ~a\n" file line col msg)) (printf "; See for more.\n" *herbie-version* url)] ["timeout" (printf "; Timeout in ~as (see --timeout option)\n" (/ (hash-ref result 'time) 1000))])))) ================================================ FILE: src/config.rkt ================================================ #lang racket (require racket/hash) (provide (all-defined-out)) ;;; Flags (define default-flags #hash([precision . ()] [setup . (search preprocess)] [localize . ()] [generate . (rr taylor proofs evaluate)] [reduce . (regimes binary-search branch-expressions)] [rules . (arithmetic polynomials fractions exponents trigonometry hyperbolic special)] [dump . ()])) (define deprecated-flags #hash([precision . (double fallback)] [setup . (simplify)] [localize . (costs errors)] [generate . (better-rr simplify)] [reduce . (avg-error simplify)] [rules . (numerics special bools branches)])) (define debug-flags #hash([generate . (egglog)] [dump . (egg rival egglog trace intermediates)] [setup . (rival2)])) (define all-flags (hash-union default-flags deprecated-flags debug-flags #:combine set-union)) (define (flag-deprecated? category flag) (set-member? (dict-ref deprecated-flags category '()) flag)) ; `hash-copy` returns a mutable hash, which makes `dict-update` invalid (define *flags* (make-parameter (make-immutable-hash (hash->list default-flags)))) (define (flag-set? class flag) (set-member? (dict-ref (*flags*) class) flag)) (define (enable-flag! category flag) (when (flag-deprecated? category flag) (warn-flag-deprecated! category flag)) (define (update cat-flags) (set-add cat-flags flag)) (*flags* (dict-update (*flags*) category update))) (define (disable-flag! category flag) (when (flag-deprecated? category flag) (warn-flag-deprecated! category flag)) (define (update cat-flags) (set-remove cat-flags flag)) (*flags* (dict-update (*flags*) category update))) (define (warn-flag-deprecated! category flag) (match* (category flag) [('precision 'double) (eprintf "The precision:double option has been removed.\n") (eprintf " The double-precision representation is specified with :precision binary64.\n") (eprintf "See for more.\n" *herbie-version*)] [('precision 'fallback) (eprintf "The precision:fallback option has been removed.\n") (eprintf " The fallback representation is specified with :precision racket.\n") (eprintf "See for more.\n" *herbie-version*)] [('setup 'simplify) (eprintf "The setup:simplify option has been removed.\n") (eprintf " Initial simplification is no longer needed.\n") (eprintf "See for more.\n" *herbie-version*)] [('generate 'better-rr) (eprintf "The generate:better-rr option has been removed.\n") (eprintf " The current recursive rewriter does not support the it.\n") (eprintf "See for more.\n" *herbie-version*)] [('generate 'simplify) (eprintf "The generate:simplify option has been removed.\n") (eprintf " Simplification is no longer performed as a separate step.\n") (eprintf "See for more.\n" *herbie-version*)] [('reduce 'simplify) (eprintf "The reduce:simplify option has been removed.\n") (eprintf " Final-simplification is no longer performed.\n") (eprintf "See for more.\n" *herbie-version*)] [('reduce 'avg-error) (eprintf "The reduce:avg-error option has been removed.\n") (eprintf " Herbie now always uses average error for pruning.\n") (eprintf "See for more.\n" *herbie-version*)] [('localize 'costs) (eprintf "The localize:costs option has been removed.\n") (eprintf " Herbie no longer performs localization.\n") (eprintf "See for more.\n" *herbie-version*)] [('localize 'errors) (eprintf "The localize:errors option has been removed.\n") (eprintf " Herbie no longer performs localization.\n") (eprintf "See for more.\n" *herbie-version*)] [('rules _) (eprintf "The rules:~a ruleset has been removed.\n") (eprintf " These rules are no longer used by Herbie.\n") (eprintf "See for more.\n" *herbie-version*)] [(_ _) (void)])) (define (changed-flags) (filter identity (for*/list ([(class flags) all-flags] [flag flags]) (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)])))) ;;; Herbie internal parameters ;; 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 4)) ;; The maximum depth for splitting the space when searching for valid areas of points. (define *max-find-range-depth* (make-parameter 12)) ;; The maximum number of consecutive skipped points for sampling valid points (define *max-skipped-points* (make-parameter 100)) ;; Maximum MPFR precision allowed during exact evaluation (define *max-mpfr-prec* (make-parameter 10000)) ;; The maximum size of an egraph (define *node-limit* (make-parameter 4000)) (define *proof-max-length* (make-parameter 200)) (define *proof-max-string-length* (make-parameter 10000)) ;; How long of a Taylor series to generate; too long and we time out (define *taylor-order-limit* (make-parameter 4)) ;; How accurate to make the binary search (define *binary-search-test-points* (make-parameter 16)) (define *binary-search-accuracy* (make-parameter 48)) ;; If `:precision` is unspecified, which representation should we use? (define *default-precision* (make-parameter 'binary64)) ;; The platform that Herbie will evaluate with. (define *platform-name* (make-parameter (if (equal? (system-type 'os) 'windows) "c-windows" "c"))) ;; Sets the number of total points for Herbie to sample. (define *reeval-pts* (make-parameter 8000)) ;; Time out for a given run. 2.5 minutes currently. (define *timeout* (make-parameter (* 1000 60 5/2))) ;; The number of variants extracted from egglog (define *egglog-variants-limit* (make-parameter 1000000)) ;; The number of iterations for the egglog search (define *default-egglog-iter-limit* (make-parameter 50)) ;;; The random seed (define the-seed #f) (define (get-seed) (or the-seed (error "Seed is not set yet!"))) (define (set-seed! seed) "Reset the random number generator to a new seed" (set! the-seed seed) (if (vector? seed) (current-pseudo-random-generator (vector->pseudo-random-generator seed)) (random-seed seed))) ;;; 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) (cond [(or (directory-exists? ".git") (file-exists? ".git")) ; gitlinks like for worktrees (define cmd (format "git ~a ~a" gitcmd (string-join args " "))) (define out (run-command cmd)) (if (equal? out "") default out)] [else default])) (define *herbie-version* "2.3") (define *herbie-commit* (git-command "rev-parse" "HEAD" #:default *herbie-version*)) (define *herbie-branch* (git-command "rev-parse" "--abbrev-ref" "HEAD" #:default "release")) ;;; The "reset" mechanism for clearing caches and such (define resetters '()) (define (register-resetter! fn) (set! resetters (cons fn resetters))) (define (reset!) (for ([fn (in-list resetters)]) (fn))) (define-syntax define/reset (syntax-rules () ; default resetter sets parameter to `value` [(_ name value) (define/reset name value (λ () (name value)))] ; initial value and resetter [(_ name value reset-fn) (define name (let ([param (make-parameter value)]) (register-resetter! reset-fn) param))])) ================================================ FILE: src/core/alt-table.rkt ================================================ #lang racket (require racket/hash) (require math/flonum) (require "../core/alternative.rkt" "../utils/common.rkt" "../utils/pareto.rkt" "../syntax/types.rkt" "../syntax/syntax.rkt" "../syntax/platform.rkt" "../syntax/batch.rkt" "points.rkt" "programs.rkt") (provide (contract-out (make-alt-table (batch? pcontext? alt? any/c . -> . alt-table?)) (atab-active-alts (alt-table? . -> . (listof alt?))) (atab-all-alts (alt-table? . -> . (listof alt?))) (atab-not-done-alts (alt-table? . -> . (listof alt?))) (atab-eval-altns (alt-table? batch? (listof alt?) context? . -> . (values any/c any/c))) (atab-add-altns (alt-table? (listof alt?) any/c any/c context? . -> . alt-table?)) (atab-set-picked (alt-table? (listof alt?) . -> . alt-table?)) (atab-completed? (alt-table? . -> . boolean?)) (atab-min-errors (alt-table? . -> . flvector?)) (alt-batch-costs (batch? context? . -> . (batchref? . -> . real?))))) ;; Public API (struct alt-table (point-idx->alts alt->point-idxs alt->done? alt->cost pcontext all) #:prefab) (define (alt-batch-costs batch ctx) (define node-cost-proc (platform-node-cost-proc (*active-platform*))) (define reprs (batch-reprs batch ctx)) (batch-recurse batch (λ (brf recurse) (define node (deref brf)) (define repr (reprs brf)) (match node [(? literal?) ((node-cost-proc node repr))] [(? symbol?) ((node-cost-proc node repr))] [(? number?) 0] ; specs [(approx _ impl) (recurse impl)] [(list (? (negate impl-exists?) impl) args ...) 0] ; specs [(list impl args ...) (define cost-proc (node-cost-proc node repr)) (apply cost-proc (map recurse args))])))) (define (make-alt-table batch pcontext initial-alt ctx) (define cost ((alt-batch-costs batch ctx) (alt-expr initial-alt))) (define errs (batchref-errors (alt-expr initial-alt) pcontext ctx)) (alt-table (for/vector #:length (pcontext-length pcontext) ([err (in-flvector errs)]) (list (pareto-point cost err (list initial-alt)))) (hasheq initial-alt (for/list ([idx (in-range (pcontext-length pcontext))]) idx)) (hasheq initial-alt #f) (hasheq initial-alt cost) pcontext (list initial-alt))) (define (atab-set-picked atab alts) (struct-copy alt-table atab [point-idx->alts (vector-copy (alt-table-point-idx->alts atab))] [alt->done? (for/fold ([alt->done? (alt-table-alt->done? atab)]) ([alt (in-list alts)]) (hash-set alt->done? alt #t))])) (define (atab-completed? atab) (andmap (curry hash-ref (alt-table-alt->done? atab)) (hash-keys (alt-table-alt->point-idxs atab)))) ;; ;; Hash/set iteration order is unspecified. Always sort extracted alternatives ;; before iterating so search decisions do not depend on table iteration order. ;; (define (order-altns altns) (sort altns exprpoint-idxs atab)))) (define (atab-all-alts atab) (order-altns (alt-table-all atab))) (define (atab-not-done-alts atab) (define altns (hash-keys (alt-table-alt->point-idxs atab))) (define not-done? (negate (curry hash-ref (alt-table-alt->done? atab)))) (order-altns (filter not-done? altns))) ;; Implementation (struct set-cover (removable coverage)) (define (atab->set-cover atab) (match-define (alt-table pnts->alts alts->pnts alt->done? alt->cost _ _) atab) (define tied (list->mutable-seteq (hash-keys alts->pnts))) (define coverage '()) (for* ([pcurve (in-vector pnts->alts)] [ppt (in-list pcurve)]) (match (pareto-point-data ppt) [(list) (error "This point has no alts which are best at it!" ppt)] [(list altn) (set-remove! tied altn)] [altns (set! coverage (cons (list->vector altns) coverage))])) (set-cover tied (list->vector coverage))) (define (set-cover-remove! sc altn) (match-define (set-cover removable coverage) sc) (set-remove! removable altn) (for ([j (in-naturals)] [s (in-vector coverage)] #:when s) (define count 0) (define last #f) (for ([i (in-naturals)] [a (in-vector s)] #:when a) (cond [(eq? a altn) (vector-set! s i #f)] [a (set! count (add1 count)) (set! last a)])) (when (= count 1) (vector-set! coverage j #f) (set-remove! removable last)))) (define ((removabilitydone? atab) alt1)) (define alt2-done? (hash-ref (alt-table-alt->done? atab) alt2)) (cond [(and (not alt1-done?) alt2-done?) #t] [(and alt1-done? (not alt2-done?)) #f] [else (define alt1-num (length (hash-ref (alt-table-alt->point-idxs atab) alt1))) (define alt2-num (length (hash-ref (alt-table-alt->point-idxs atab) alt2))) (cond [(< alt1-num alt2-num) #t] [(> alt1-num alt2-num) #f] [else (define alt1-cost (hash-ref (alt-table-alt->cost atab) alt1)) (define alt2-cost (hash-ref (alt-table-alt->cost atab) alt2)) (cond [(< alt1-cost alt2-cost) #f] [(< alt2-cost alt1-cost) #t] [else (exprset-cover atab)) (define removability (sort (set->list (set-cover-removable sc)) (removabilityalts alt->point-idxs alt->done? alt->cost pctx _) atab) (define pnt-idx->alts* (for/vector #:length (vector-length point-idx->alts) ([pcurve (in-vector point-idx->alts)]) (pareto-map (curry remq* altns) pcurve))) (struct-copy alt-table atab [point-idx->alts pnt-idx->alts*] [alt->point-idxs (hash-remove* alt->point-idxs altns)] [alt->done? (hash-remove* alt->done? altns)] [alt->cost (hash-remove* alt->cost altns)])) (define (atab-eval-altns atab batch altns ctx) (define brfs (map alt-expr altns)) (define errss (batch-errors batch brfs (alt-table-pcontext atab) ctx)) (define costs (map (alt-batch-costs batch ctx) brfs)) (values errss costs)) (define (atab-add-altns atab altns errss costs ctx) (define atab* (for/fold ([atab atab]) ([altn (in-list altns)] [errs (in-list errss)] [cost (in-list costs)]) (atab-add-altn atab altn errs cost ctx))) (define atab** (struct-copy alt-table atab* [alt->point-idxs (invert-index (alt-table-point-idx->alts atab*))])) (define atab*** (atab-prune atab**)) (struct-copy alt-table atab*** [alt->point-idxs (invert-index (alt-table-point-idx->alts atab***))] [all (set-union (alt-table-all atab) (hash-keys (alt-table-alt->point-idxs atab***)))])) (define (invert-index point-idx->alts) (define alt->points* (make-hasheq)) (for ([pcurve (in-vector point-idx->alts)] [idx (in-naturals)]) (for* ([ppt (in-list pcurve)] [alt (in-list (pareto-point-data ppt))]) (hash-update! alt->points* alt (λ (v) (cons idx v)) '()))) (make-immutable-hasheq (hash->list alt->points*))) (define (atab-add-altn atab altn errs cost ctx) (match-define (alt-table point-idx->alts alt->point-idxs alt->done? alt->cost pcontext _) atab) (define max-valid-bits (representation-total-bits (context-repr ctx))) ;; Check whether altn is already inserted into atab (match (hash-has-key? alt->point-idxs altn) [#f (define point-idx->alts* (for/vector #:length (vector-length point-idx->alts) ([pcurve (in-vector point-idx->alts)] [err (in-flvector errs)]) (cond [(<= err max-valid-bits) ; Only include points if they are valid (define ppt (pareto-point cost err (list altn))) (pareto-union (list ppt) pcurve #:combine append)] [else pcurve]))) (alt-table point-idx->alts* (hash-set alt->point-idxs altn #f) (hash-set alt->done? altn #f) (hash-set alt->cost altn cost) pcontext #f)] [_ atab])) (define (atab-min-errors atab) (define pnt-idx->alts (alt-table-point-idx->alts atab)) (for/flvector #:length (pcontext-length (alt-table-pcontext atab)) ([curve (in-vector pnt-idx->alts)]) ;; Curve is sorted so lowest error is first (pareto-point-error (first curve)))) ================================================ FILE: src/core/alternative.rkt ================================================ #lang racket (require "../syntax/platform.rkt" "../syntax/batch.rkt") (provide (struct-out alt) (struct-out sp) make-alt alt-cost alt-map unbatchify-alts) ;; A splitpoint (sp a b pt) means we should use alt a if b < pt ;; The last splitpoint uses +nan.0 for pt and represents the "else" (struct sp (cidx bexpr point) #:prefab) ;; Alts are an expression plus a derivation for it. (struct alt (expr event prevs) #:prefab) (define (make-alt expr) (alt expr 'start '())) (define (alt-cost altn repr) (define expr-cost (platform-cost-proc (*active-platform*))) (expr-cost (alt-expr altn) repr)) (define (alt-map f altn) (f (struct-copy alt altn [prevs (map (curry alt-map f) (alt-prevs altn))]))) ;; Converts batchrefs of altns into expressions, assuming that batchrefs refer to batch (define (unbatchify-alts batch altns) (define exprs (batch-exprs batch)) (define (unmunge-event event) (match event [(list 'evaluate (? batchref? start-expr)) (list 'evaluate (exprs start-expr))] [(list 'taylor (? batchref? start-expr) name var) (list 'taylor (exprs start-expr) name var)] [(list 'rr (? batchref? start-expr) (? batchref? end-expr) input proof) (define proof* (and proof (map exprs proof))) (list 'rr (exprs start-expr) (exprs end-expr) input proof*)] [(list 'regimes splitpoints) (list 'regimes (for/list ([spt (in-list splitpoints)]) (struct-copy sp spt [bexpr (exprs (sp-bexpr spt))])))] [_ event])) (define (unmunge altn) (define expr (alt-expr altn)) (define expr* (if (batchref? expr) (exprs expr) expr)) (define event* (unmunge-event (alt-event altn))) (struct-copy alt altn [expr expr*] [event event*])) (map (curry alt-map unmunge) altns)) ================================================ FILE: src/core/arrays.rkt ================================================ #lang racket (require racket/hash racket/list "../syntax/types.rkt") (provide flatten-arrays-for-rival) ;; Flatten array inputs/outputs into scalar inputs/outputs for Rival. ;; Returns: ;; - flattened specs ;; - flattened contexts ;; - flattened precondition ;; - point assembler (original point -> flattened point) ;; - output assembler (flattened outputs -> original outputs) ;; - flattened output reprs (define (flatten-arrays-for-rival specs ctxs pre) (define orig-vars (context-vars (first ctxs))) (define orig-reprs (map context-repr ctxs)) (define orig-var-reprs (context-var-reprs (first ctxs))) (define taken (apply mutable-seteq orig-vars)) (define (fresh base) (let loop ([i 0]) (define candidate (string->symbol (format "~a_~a" base i))) (if (set-member? taken candidate) (loop (add1 i)) (begin (set-add! taken candidate) candidate)))) (define (leaf-reprs repr) (if (array-representation? repr) (append* (for/list ([_ (in-range (array-representation-len repr))]) (leaf-reprs (array-representation-elem repr)))) (list repr))) (define (fresh-tree base repr) (if (array-representation? repr) (let-values ([(elems vars reprs) (for/lists (elems vars reprs) ([_ (in-range (array-representation-len repr))]) (fresh-tree base (array-representation-elem repr)))]) (values `(array ,@elems) (append* vars) (append* reprs))) (let ([v (fresh base)]) (values v (list v) (list repr))))) (define (flatten-by-repr expr repr) (if (array-representation? repr) (match-let ([`(array ,elems ...) expr]) (append* (for/list ([elem (in-list elems)]) (flatten-by-repr elem (array-representation-elem repr))))) (list expr))) (define (build-value next repr) (if (array-representation? repr) (for/vector #:length (array-representation-len repr) ([_ (in-range (array-representation-len repr))]) (build-value next (array-representation-elem repr))) (next))) (define env (make-hasheq)) (define new-vars '()) (define new-var-reprs '()) (for ([v orig-vars] [r orig-var-reprs]) (cond [(array-representation? r) (define base (symbol->string v)) (define-values (tree vars reprs) (fresh-tree base r)) (hash-set! env v tree) (set! new-vars (append new-vars vars)) (set! new-var-reprs (append new-var-reprs reprs))] [else (hash-set! env v v) (set! new-vars (append new-vars (list v))) (set! new-var-reprs (append new-var-reprs (list r)))])) (define (lower-arr expr) (match expr [(? number?) expr] [(? symbol? s) (hash-ref env s s)] [`(,op ,args ...) (define lowered `(,op ,@(map lower-arr args))) (match lowered [`(ref (array ,elems ...) ,idx) (list-ref elems idx)] [_ lowered])])) (define new-specs '()) (define new-reprs '()) (for ([spec (in-list specs)] [repr (in-list orig-reprs)]) (define lowered (lower-arr spec)) (cond [(array-representation? repr) (define comps (flatten-by-repr lowered repr)) (define reprs (leaf-reprs repr)) (set! new-specs (append new-specs comps)) (set! new-reprs (append new-reprs reprs))] [else (set! new-specs (append new-specs (list lowered))) (set! new-reprs (append new-reprs (list repr)))])) (define new-pre (lower-arr pre)) (define ctxs* (for/list ([ctx (in-list ctxs)]) (match-define (context _ repr _) ctx) (context new-vars (if (array-representation? repr) (array-representation-base repr) repr) new-var-reprs))) (define (assemble-point pt) (define idx 0) (define (next) (begin0 (vector-ref pt idx) (set! idx (add1 idx)))) (for/vector #:length (length orig-var-reprs) ([repr (in-list orig-var-reprs)]) (build-value next repr))) (define (assemble-output outs) (define outputs (if (vector? outs) (vector->list outs) outs)) (define idx 0) (define (next) (begin0 (list-ref outputs idx) (set! idx (add1 idx)))) (for/list ([repr (in-list orig-reprs)]) (if (array-representation? repr) (build-value next repr) (next)))) (values new-specs ctxs* new-pre assemble-point assemble-output new-reprs)) (module+ test (require rackunit) (define vec2 (make-array-representation #:elem #:len 2)) (define ctx (context '(x) (list vec2))) (let-values ([(specs* _ pre* _assemble-point _assemble-output _reprs*) (flatten-arrays-for-rival (list '(ref x 1)) (list ctx) '(< (ref x 0) (ref x 1)))]) (check-equal? specs* '(x_1)) (check-equal? pre* '(< x_0 x_1))) (define mat2 (make-array-representation #:elem vec2 #:len 2)) (define nested-ctx (context '(x) (list mat2))) (let-values ([(specs* _ pre* assemble-point _assemble-output _reprs*) (flatten-arrays-for-rival (list '(ref (ref x 1) 0)) (list nested-ctx) '(< (ref (ref x 0) 1) (ref (ref x 1) 0)))]) (check-equal? specs* '(x_2)) (check-equal? pre* '(< x_1 x_2)) (check-equal? (assemble-point #(1 2 3 4)) #(#(#(1 2) #(3 4))))) (let-values ([(specs* _ctxs* _pre* _assemble-point assemble-output reprs*) (flatten-arrays-for-rival (list '(array (array 1 2) (array 3 4))) (list (context '() mat2 '())) 'TRUE)]) (check-equal? specs* '(1 2 3 4)) (check-equal? reprs* (list )) (check-equal? (assemble-output '(10 11 12 13)) (list #(#(10 11) #(12 13)))))) ================================================ FILE: src/core/batch-reduce.rkt ================================================ #lang racket (require "../syntax/batch.rkt" "../utils/common.rkt" "programs.rkt") (provide batch-reduce) (define global-batch (make-parameter #f)) ;; This is a transcription of egg-herbie/src/math.rs, lines 97-149 (define (batch-eval-application batch) (define exact-value? (conjoin number? exact?)) (define (eval-application brf recurse) (match (deref brf) [(? exact-value? val) val] ;; this part is not naive in rewriting. should be considered for the future [(list '+ (app recurse (? exact-value? as)) ...) (apply + as)] [(list '- (app recurse (? exact-value? as)) ...) (apply - as)] [(list '* (app recurse (? exact-value? as)) ...) (apply * as)] [(list '/ (app recurse (? exact-value? num)) (app recurse (? exact-value? den))) (and (not (zero? den)) (/ num den))] [(list 'neg (app recurse (? exact-value? arg))) (- arg)] [(list 'pow (app recurse (? exact-value? a)) (app recurse (? exact-value? b))) (cond [(and (zero? b) (not (zero? a))) 1] [(and (zero? a) (positive? b)) 0] [(and (not (zero? a)) (integer? b)) (expt a b)] [(= a -1) (if (even? (numerator b)) 1 -1)] [(= a 1) 1] [else #f])] [(list 'sqrt (app recurse (? exact-value? a))) (define s1 (sqrt (numerator a))) (define s2 (sqrt (denominator a))) (and (real? s1) (real? s2) (exact? s1) (exact? s2) (/ s1 s2))] [(list 'cbrt (app recurse (? exact-value? a))) (define inexact-num (inexact->exact (expt (abs (numerator a)) 1/3))) (define inexact-den (inexact->exact (expt (abs (denominator a)) 1/3))) (and (real? inexact-num) (real? inexact-den) (= (expt inexact-num 3) (abs (numerator a))) (= (expt inexact-den 3) (abs (denominator a))) (* (sgn a) (/ inexact-num inexact-den)))] [(list 'fabs (app recurse (? exact-value? a))) (abs a)] [(list 'floor (app recurse (? exact-value? a))) (floor a)] [(list 'ceil (app recurse (? exact-value? a))) (ceiling a)] [(list 'round (app recurse (? exact-value? a))) (round a)] [(list 'exp (app recurse 0)) 1] [(list 'log (app recurse 1)) 0] [_ #f])) (batch-recurse batch eval-application)) (define (batch-reduce batch) ;; Dependencies (define eval-application (batch-eval-application batch)) (define gather-multiplicative-terms (batch-gather-multiplicative-terms batch eval-application)) (letrec ([reduce-node (batch-recurse batch (lambda (brf recurse) (define brf* (reduce-evaluation brf)) (match (deref brf*) [(? number?) brf*] [(? symbol?) brf*] [(or `(+ ,_ ...) `(- ,_ ...) `(neg ,_)) (make-addition-node (combine-aterms (gather-additive-terms brf*)))] [(or `(* ,_ ...) `(/ ,_ ...) `(cbrt ,_) `(pow ,_ ,(app deref (? (conjoin rational? (negate even-denominator?)))))) (make-multiplication-node (combine-mterms (gather-multiplicative-terms brf*)))] [(list 'exp (app deref (list '* c (app deref (list 'log x))))) (define rewrite (batch-add! batch `(pow ,x ,c))) (recurse rewrite)] [else (reduce-inverses brf*)])))] [gather-additive-terms (batch-recurse batch (lambda (brf recurse) (match (deref brf) [(? number? n) `((,n ,(batch-push! batch 1)))] [(? symbol?) `((1 ,brf))] [`(+ ,args ...) (append-map recurse args)] [`(neg ,arg) (map negate-term (recurse arg))] [`(- ,arg ,args ...) (append (recurse arg) (map negate-term (append-map recurse args)))] ; Prevent fall-through to the next case [`(/ ,arg) `((1 ,brf))] [`(/ ,arg ,args ...) (for/list ([term (recurse arg)]) (list (car term) (reduce-node (batch-add! batch (list* '/ (cadr term) args)))))] [else `((1 ,brf))])))]) ;; Actual code (define (reduce brf recurse) (parameterize ([global-batch batch]) (define node (deref brf)) (match node [(? number?) brf] [(? symbol?) brf] [`(,op ,args ...) (define args* (map recurse args)) (define brf* (batch-add! batch (list* op args*))) (define val (eval-application brf*)) (when val ;; convert to batchref if result is not #f (set! val (batch-push! batch val))) (or val (reduce-node brf*))]))) (batch-recurse batch reduce))) (define (reduce-evaluation brf) (define batch (batchref-batch brf)) (define (pi-multiple expr) (match expr [`(PI) 1] [`(* ,(app deref (? rational? coeff)) ,(app deref '(PI))) coeff] [`(* ,(app deref '(PI)) ,(app deref (? rational? coeff))) coeff] [`(/ ,(app deref '(PI)) ,(app deref (? rational? denom))) (/ denom)] [_ #f])) (define node* (match (deref brf) [(list 'sin (app deref 0)) 0] [(list 'cos (app deref 0)) 1] [(list 'sin (app deref (app pi-multiple 1))) 0] [(list 'cos (app deref (app pi-multiple 1))) -1] [(list 'exp (app deref 1)) '(E)] [(list 'tan (app deref 0)) 0] [(list 'sinh (app deref 0)) 0] [(list 'log (app deref (list 'E))) 1] [(list 'exp (app deref 0)) 1] [(list 'tan (app deref (app pi-multiple 1))) 0] [(list 'cosh (app deref 0)) 1] [(list 'cos (app deref (app pi-multiple 1/6))) '(/ (sqrt 3) 2)] [(list 'tan (app deref (app pi-multiple 1/3))) '(sqrt 3)] [(list 'tan (app deref (app pi-multiple 1/4))) 1] [(list 'cos (app deref (app pi-multiple 1/2))) 0] [(list 'tan (app deref (app pi-multiple 1/6))) '(/ 1 (sqrt 3))] [(list 'sin (app deref (app pi-multiple 1/3))) '(/ (sqrt 3) 2)] [(list 'sin (app deref (app pi-multiple 1/6))) 1/2] [(list 'sin (app deref (app pi-multiple 1/4))) '(/ (sqrt 2) 2)] [(list 'sin (app deref (app pi-multiple 1/2))) 1] [(list 'cos (app deref (app pi-multiple 1/3))) 1/2] [(list 'cos (app deref (app pi-multiple 1/4))) '(/ (sqrt 2) 2)] [node node])) (batch-add! batch node*)) (define (reduce-inverses brf) (match (deref brf) [(list 'tanh (app deref (list 'atanh x))) x] [(list 'cosh (app deref (list 'acosh x))) x] [(list 'sinh (app deref (list 'asinh x))) x] [(list 'acos (app deref (list 'cos x))) x] [(list 'asin (app deref (list 'sin x))) x] [(list 'atan (app deref (list 'tan x))) x] [(list 'tan (app deref (list 'atan x))) x] [(list 'cos (app deref (list 'acos x))) x] [(list 'sin (app deref (list 'asin x))) x] [(list 'pow x (app deref 1)) x] [(list 'log (app deref (list 'exp x))) x] [(list 'exp (app deref (list 'log x))) x] [(list 'cbrt (app deref (list 'pow x (app deref 3)))) x] [(list 'pow (app deref (list 'cbrt x)) (app deref 3)) x] [_ brf])) (define (negate-term term) (cons (- (car term)) (cdr term))) (define (even-denominator? x) (even? (denominator x))) (define (batch-gather-multiplicative-terms batch eval-application) (define (nan-term) `(+nan.0 . ((1 . ,(batch-push! batch 1))))) (define (gather-multiplicative-terms brf recurse) (match (deref brf) [+nan.0 (nan-term)] [(? number? n) (list n)] [(? symbol?) `(1 . ((1 . ,brf)))] [`(neg ,arg) (define terms (recurse arg)) (if (eq? (car terms) +nan.0) (nan-term) (negate-term terms))] [`(* ,args ...) (define terms (map recurse args)) (if (ormap (curry eq? +nan.0) (map car terms)) (nan-term) (cons (apply * (map car terms)) (append-map cdr terms)))] [`(/ ,arg) (define term (recurse arg)) (if (member (car term) '(0 +nan.0)) (nan-term) (cons (/ (car term)) (map negate-term (cdr term))))] [`(/ ,arg ,args ...) (define num (recurse arg)) (define dens (map recurse args)) (if (or (eq? (car num) +nan.0) (ormap (compose (curryr member '(0 +nan.0)) car) dens)) (nan-term) (cons (apply / (car num) (map car dens)) (append (cdr num) (map negate-term (append-map cdr dens)))))] [`(cbrt ,arg) (define terms (recurse arg)) (cond [(equal? (car terms) +nan.0) (nan-term)] [else (define exact-cbrt (eval-application (batch-add! batch (list 'cbrt (car terms))))) (if exact-cbrt (cons exact-cbrt (for/list ([term (cdr terms)]) (cons (/ (car term) 3) (cdr term)))) (list* 1 (cons 1 (batch-add! batch `(cbrt ,(car terms)))) (for/list ([term (cdr terms)]) (cons (/ (car term) 3) (cdr term)))))])] [`(pow ,arg ,(app deref 0)) (define terms (recurse arg)) (if (equal? (car terms) +nan.0) (nan-term) `(1 . ()))] [`(pow ,arg ,(app deref (? (conjoin rational? (negate even-denominator?)) a))) (define terms (recurse arg)) (define exact-pow (match (car terms) [+nan.0 +nan.0] [x (eval-application (batch-add! batch (list 'pow x a)))])) (if exact-pow (cons exact-pow (for/list ([term (cdr terms)]) (cons (* a (car term)) (cdr term)))) (list* 1 (cons a (batch-push! batch (car terms))) (for/list ([term (cdr terms)]) (cons (* a (car term)) (cdr term)))))] [_ `(1 . ((1 . ,brf)))])) (batch-recurse batch gather-multiplicative-terms)) (define (combine-aterms terms) (define h (make-hash)) (for ([term terms]) (hash-update! h (cadr term) (λ (sum) (+ (car term) sum)) 0)) (sort (reap [sow] (for ([(k v) (in-hash h)] #:when (not (= v 0))) (sow (cons v k)))) exprexpr term) (match term [`(1 . ,x) x] [`(,x . ,(app deref 1)) (batch-push! (global-batch) x)] [`(-1 . ,x) (batch-add! (global-batch) `(neg ,x))] [`(,coeff . ,x) (batch-add! (global-batch) `(* ,coeff ,x))])) (define (make-addition-node terms) (define-values (pos neg) (partition (λ (x) (and (real? (car x)) (positive? (car x)))) terms)) (cond [(and (null? pos) (null? neg)) (batch-push! (global-batch) 0)] [(null? pos) (batch-add! (global-batch) `(neg ,(make-addition-node* (map negate-term neg))))] [(null? neg) (make-addition-node* pos)] [else (batch-add! (global-batch) `(- ,(make-addition-node* pos) ,(make-addition-node* (map negate-term neg))))])) (define (make-addition-node* terms) (match terms ['() (batch-push! (global-batch) 0)] [`(,term) (aterm->expr term)] [`(,term ,terms ...) (batch-add! (global-batch) `(+ ,(aterm->expr term) ,(make-addition-node terms)))])) (define (make-multiplication-node term) (match (cons (car term) (make-multiplication-subnode (cdr term))) [(cons +nan.0 e) (batch-push! (global-batch) '(NAN))] [(cons 0 e) (batch-push! (global-batch) 0)] [(cons 1 '()) (batch-push! (global-batch) 1)] [(cons 1 e) e] [(cons a (app deref 1)) (batch-push! (global-batch) a)] [(cons a (app deref (list '/ (app deref 1) denom))) (batch-add! (global-batch) `(/ ,a ,denom))] [(cons a '()) (batch-push! (global-batch) a)] [(cons a e) (batch-add! (global-batch) `(* ,a ,e))])) (define (make-multiplication-subnode terms) (make-multiplication-subsubsubnode (list (cons 1 (mterm->expr (cons 1 (make-multiplication-subsubnode terms))))))) (define (make-multiplication-subsubnode terms) (define-values (pos neg) (partition (compose positive? car) terms)) (cond [(and (null? pos) (null? neg)) (batch-push! (global-batch) 1)] [(null? pos) (batch-add! (global-batch) `(/ 1 ,(make-multiplication-subsubsubnode (map negate-term neg))))] [(null? neg) (make-multiplication-subsubsubnode pos)] [else (batch-add! (global-batch) `(/ ,(make-multiplication-subsubsubnode pos) ,(make-multiplication-subsubsubnode (map negate-term neg))))])) (define (make-multiplication-subsubsubnode terms) (match terms ['() (batch-push! (global-batch) 1)] [`(,term) (mterm->expr term)] [`(,term ,terms ...) (batch-add! (global-batch) `(* ,(mterm->expr term) ,(make-multiplication-subsubsubnode terms)))])) (define (mterm->expr term) (match term [(cons 1 x) x] [(cons -1 x) (batch-add! (global-batch) `(/ 1 ,x))] [(cons 1/2 x) (batch-add! (global-batch) `(sqrt ,x))] [(cons -1/2 x) (batch-add! (global-batch) `(/ 1 (sqrt ,x)))] [(cons 1/3 x) (batch-add! (global-batch) `(cbrt ,x))] [(cons -1/3 x) (batch-add! (global-batch) `(/ 1 (cbrt ,x)))] [(cons power x) (batch-add! (global-batch) `(pow ,x ,power))])) (module+ test (require rackunit) (define batch (batch-empty)) (define evaluator (batch-eval-application batch)) (define (evaluator-results expr) (evaluator (batch-add! batch expr))) ;; Checks for batch-eval-application (check-equal? (evaluator-results '(+ 1 1)) 2) (check-equal? (evaluator-results '(+)) 0) (check-equal? (evaluator-results '(/ 1 0)) #f) ; Not valid (check-equal? (evaluator-results '(cbrt 1)) 1) (check-equal? (evaluator-results '(log 1)) 0) (check-equal? (evaluator-results '(exp 2)) #f) ; Not exact ;; Checks for batch-reduce-evaluation (define (reducer-results expr) ((batch-exprs batch) (reduce-evaluation (batch-add! batch expr)))) (check-equal? (reducer-results '(cos (/ (PI) 6))) '(/ (sqrt 3) 2)) (check-equal? (reducer-results '(sin (/ (PI) 4))) '(/ (sqrt 2) 2)) (check-equal? (reducer-results '(cos (PI))) -1) (check-equal? (reducer-results '(exp 1)) '(E)) ;; Checks for batch-reduce-inverses (define (inverse-reducer-results expr) ((batch-exprs batch) (reduce-inverses (batch-add! batch expr)))) (check-equal? (inverse-reducer-results '(cosh (acosh x))) 'x) (check-equal? (inverse-reducer-results '(tanh (atanh x))) 'x) (check-equal? (inverse-reducer-results '(sinh (asinh x))) 'x) (check-equal? (inverse-reducer-results '(acos (cos x))) 'x) (check-equal? (inverse-reducer-results '(asin (sin x))) 'x) (check-equal? (inverse-reducer-results '(asin (sin x))) 'x) (check-equal? (inverse-reducer-results '(atan (tan x))) 'x) (check-equal? (inverse-reducer-results '(tan (atan x))) 'x) (check-equal? (inverse-reducer-results '(cos (acos x))) 'x) (check-equal? (inverse-reducer-results '(sin (asin x))) 'x) (check-equal? (inverse-reducer-results '(pow x 1)) 'x) (check-equal? (inverse-reducer-results '(log (exp x))) 'x) (check-equal? (inverse-reducer-results '(exp (log x))) 'x) (check-equal? (inverse-reducer-results '(cbrt (pow x 3))) 'x) (check-equal? (inverse-reducer-results '(pow (cbrt x) 3)) 'x) ;; Checks for batch-reduce (define reduce (batch-reduce batch)) (define (reduce-results expr) ((batch-exprs batch) (reduce (batch-add! batch expr)))) (check-equal? '(- (pow (+ 1 x) 2) 1) (reduce-results '(- (* (+ x 1) (+ x 1)) 1))) (check-equal? '(neg (* 2 (/ 1 x))) (reduce-results '(+ (/ 1 (neg x)) (/ 1 (neg x))))) (check-equal? '(- (pow (- 1 (/ 1 x)) 2) 1) (reduce-results '(- (* (+ (/ 1 (neg x)) 1) (+ (/ 1 (neg x)) 1)) 1))) (check-equal? '(pow (- 1 (/ 1 x)) 2) (reduce-results '(* (+ (/ 1 (neg x)) 1) (+ (/ 1 (neg x)) 1)))) (check-equal? '(+ (* 2 (/ 1 x)) (/ 1 (pow x 2))) (reduce-results '(+ (* (/ 1 x) (/ 1 x)) (+ (/ 1 x) (/ 1 x))))) (check-equal? '(+ (* 2 (/ 1 x)) (/ 1 (pow x 2))) (reduce-results '(+ (* (/ 1 x) (/ 1 x)) (+ (/ 1 x) (/ 1 x))))) (check-equal? '(/ 1 (* (cbrt 2) (cbrt a))) (reduce-results '(pow (+ a a) -1/3)))) ================================================ FILE: src/core/bsearch.rkt ================================================ #lang racket (require math/bigfloat racket/random) (require "../config.rkt" "../core/alternative.rkt" "../utils/common.rkt" "../utils/timeline.rkt" "../utils/errors.rkt" "../syntax/float.rkt" "../utils/pretty-print.rkt" "../syntax/types.rkt" "../syntax/syntax.rkt" "../syntax/platform.rkt" "../syntax/batch.rkt" "compiler.rkt" "regimes.rkt" "../syntax/rival.rkt" "sampling.rkt" "points.rkt" "programs.rkt") (provide combine-alts combine-alts/binary regimes-pcontext-masks) (module+ test (require rackunit)) (define (finish-combine-alts batch alts brf splitindices splitpoints ctx) (define splitpoints* (append splitpoints (list (sp (si-cidx (last splitindices)) brf +nan.0)))) (define reprs (batch-reprs batch ctx)) (define brf* (for/fold ([brf (alt-expr (list-ref alts (sp-cidx (last splitpoints*))))]) ([splitpoint (cdr (reverse splitpoints*))]) (define repr (reprs (sp-bexpr splitpoint))) (define if-impl (get-fpcore-impl 'if '() (list (get-representation 'bool) repr repr))) (define <=-impl (get-fpcore-impl '<= '() (list repr repr))) (define lit-brf (batch-add! batch (literal (repr->real (sp-point splitpoint) repr) (representation-name repr)))) (define cmp-brf (batch-add! batch `(,<=-impl ,(sp-bexpr splitpoint) ,lit-brf))) (batch-add! batch `(,if-impl ,cmp-brf ,(alt-expr (list-ref alts (sp-cidx splitpoint))) ,brf)))) ;; We don't want unused alts in our history! (define-values (alts* splitpoints**) (remove-unused-alts alts splitpoints*)) (alt brf* (list 'regimes splitpoints**) alts*)) (define (combine-alts batch best-option ctx) (match-define (option splitindices alts pts brf) best-option) (define splitpoints (sindices->spoints/left batch pts brf splitindices ctx)) (finish-combine-alts batch alts brf splitindices splitpoints ctx)) (define (combine-alts/binary batch best-option start-prog ctx pcontext) (match-define (option splitindices alts pts brf) best-option) (define splitpoints (sindices->spoints/binary batch pts brf alts splitindices start-prog ctx pcontext)) (finish-combine-alts batch alts brf splitindices splitpoints ctx)) (define (remove-unused-alts alts splitpoints) (for/fold ([alts* '()] [splitpoints* '()]) ([splitpoint splitpoints]) (define alt (list-ref alts (sp-cidx splitpoint))) ;; It's important to snoc the alt in order for the indices not to change (define alts** (remove-duplicates (append alts* (list alt)))) (define splitpoint* (struct-copy sp splitpoint [cidx (index-of alts** alt)])) (define splitpoints** (append splitpoints* (list splitpoint*))) (values alts** splitpoints**))) ;; Invariant: (pred p1) and (not (pred p2)) (define (binary-search-floats pred p1 p2 repr ulps) (cond [(<= (ulps->bits (ulps p1 p2)) (*binary-search-accuracy*)) (timeline-push! 'stop "narrow-enough" 1) (values p1 p2)] [else (define p3 (midpoint p1 p2 repr)) (define cmp ;; Sampling error: don't know who's better (with-handlers ([exn:fail:user:herbie:sampling? (const 'fail)]) (pred p3))) (cond [(eq? cmp 'fail) (timeline-push! 'stop "predicate-failed" 1) (values p1 p2)] [(negative? cmp) (binary-search-floats pred p3 p2 repr ulps)] [(positive? cmp) (binary-search-floats pred p1 p3 repr ulps)] ;; cmp = 0 usually means sampling failed, so we give up [else (timeline-push! 'stop "predicate-same" 1) (values p1 p2)])])) (define (extract-subexpression batch brf var pattern-brf ctx) (define var-brf (batch-add! batch var)) (if (= (batchref-idx pattern-brf) (batchref-idx var-brf)) brf (let () (define free-vars (batch-free-vars batch)) (define body-brf (batch-replace-subexpr batch brf pattern-brf var-brf)) (define vars* (set-subtract (list->set (context-vars ctx)) (free-vars pattern-brf))) (and (subset? (free-vars body-brf) (set-add vars* var)) body-brf)))) (define (deterministic-branch-var ctx) (define used-vars (list->set (context-vars ctx))) (let loop ([n 0]) (define var (string->symbol (format "branch-~a" n))) (if (set-member? used-vars var) (loop (add1 n)) var))) (define (prepend-argument evaluator val pcontext) (define pts (for/list ([(pt ex) (in-pcontext pcontext)]) pt)) ; new-sampler returns: (cons (cons val pts) hint) ; Since the sampler does not call rival-analyze, the hint is set to #f (define (new-sampler) (values (vector-append (vector val) (random-ref pts)) #f)) (define-values (results _) (batch-prepare-points evaluator new-sampler)) (apply mk-pcontext results)) (define/reset *prepend-arguement-cache* (make-hash)) (define (cache-get-prepend v brf macro) (define key (cons brf v)) (hash-ref! (*prepend-arguement-cache*) key (lambda () (macro v)))) ;; Accepts a list of sindices in one indexed form and returns the ;; proper interior 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/contract (sindices->spoints/left batch points brf sindices ctx) (-> batch? (listof vector?) batchref? (listof si?) context? (listof sp?)) (define repr ((batch-reprs batch ctx) brf)) (define eval-expr (compose (curryr vector-ref 0) (compile-batch batch (list brf) ctx))) (define (left-point p1 p2) (define left ((representation-repr->bf repr) p1)) (define right ((representation-repr->bf repr) p2)) (define out ; TODO: Try using bigfloat-pick-point here? (if (bfnegative? left) (bigfloat-interval-shortest left (bfmin (bf/ left 2.bf) right)) (bigfloat-interval-shortest left (bfmin (bf* left 2.bf) right)))) ;; It's important to return something strictly less than right (if (bf= out right) p1 ((representation-bf->repr repr) out))) (for/list ([si1 sindices] [si2 (cdr sindices)]) (define p1 (eval-expr (list-ref points (sub1 (si-pidx si1))))) (define p2 (eval-expr (list-ref points (si-pidx si1)))) (define timeline-stop! (timeline-start! 'bstep (value->json p1 repr) (value->json p2 repr))) (define split-at (left-point p1 p2)) (timeline-stop!) (timeline-push! 'method "left-value") (sp (si-cidx si1) brf split-at))) (define/contract (sindices->spoints/binary batch points brf alts sindices start-prog ctx pcontext) (-> batch? (listof vector?) batchref? (listof alt?) (listof si?) any/c context? pcontext? (listof sp?)) (define repr ((batch-reprs batch ctx) brf)) (define ulps (repr-ulps repr)) (define eval-expr (compose (curryr vector-ref 0) (compile-batch batch (list brf) ctx))) (define brf-node (deref brf)) (define var (if (symbol? brf-node) brf-node (deterministic-branch-var ctx))) (define ctx* (context-extend ctx var repr)) (define progs (for/list ([alt (in-list alts)]) (extract-subexpression batch (alt-expr alt) var brf ctx))) (define start-prog-sub (extract-subexpression batch start-prog var brf ctx)) (unless (and start-prog-sub (andmap identity progs)) (raise-user-error 'sindices->spoints/binary "mainloop called binary splitpoint search without extractable critical subexpressions")) (define spec-brfs (batch-to-spec! batch (list start-prog))) (define start-real-compiler (make-real-compiler batch spec-brfs (list ctx*))) (define (prepend-macro v) (prepend-argument start-real-compiler v pcontext)) (define (find-split si1 si2 p1 p2) (define brf1 (list-ref progs (si-cidx si1))) (define brf2 (list-ref progs (si-cidx si2))) (define eval-errors (compile-batch batch (list brf1 brf2) ctx*)) (define score-ulps (repr-ulps (context-repr ctx*))) (define (pred v) (define pctx (parameterize ([*num-points* (*binary-search-test-points*)]) (cache-get-prepend v brf prepend-macro))) (for/sum ([(pt ex) (in-pcontext pctx)]) (match-define (vector out1 out2) (eval-errors pt)) (- (ulps->bits (score-ulps out1 ex)) (ulps->bits (score-ulps out2 ex))))) (define-values (bp1 _) (binary-search-floats pred p1 p2 repr ulps)) bp1) (for/list ([si1 sindices] [si2 (cdr sindices)]) (define p1 (eval-expr (list-ref points (sub1 (si-pidx si1))))) (define p2 (eval-expr (list-ref points (si-pidx si1)))) (define timeline-stop! (timeline-start! 'bstep (value->json p1 repr) (value->json p2 repr))) (define split-at (find-split si1 si2 p1 p2)) (timeline-stop!) (timeline-push! 'method "binary-search") (sp (si-cidx si1) brf split-at))) (define (regimes-pcontext-masks pcontext splitpoints alts ctx) (define num-alts (length alts)) (define num-points (pcontext-length pcontext)) (define bexpr (sp-bexpr (car splitpoints))) (define ctx* (struct-copy context ctx [repr (repr-of bexpr ctx)])) (define prog (compile-prog bexpr ctx*)) (define masks (build-vector num-alts (λ (_) (make-vector num-points #f)))) (for ([(pt _) (in-pcontext pcontext)] [idx (in-naturals)]) (define val (prog pt)) (for/first ([right (in-list splitpoints)] #:when (or (equal? (sp-point right) +nan.0) (<=/total val (sp-point right) (context-repr ctx*)))) (vector-set! (vector-ref masks (sp-cidx right)) idx #t))) masks) ================================================ FILE: src/core/compiler.rkt ================================================ #lang racket (require "../syntax/syntax.rkt" "../syntax/types.rkt" "../syntax/platform.rkt" "../syntax/float.rkt" "../utils/timeline.rkt" "../syntax/batch.rkt") (provide compile-progs compile-batch compile-prog) ;; Interpreter taking a narrow IR ;; ``` ;; ::= #( ..+) ;; ``` ;; Must also provide the input variables for the program(s) ;; as well as the indices of the roots to extract. (define (make-progs-interpreter tvec rootvec args vregs) (define rootlen (vector-length rootvec)) (define vregs-len (vector-length tvec)) (define (compiled-prog args*) (vector-copy! args 0 args*) (for ([thunk (in-vector tvec)] [n (in-range vregs-len)]) (vector-set! vregs n (thunk))) (for/vector #:length rootlen ([root (in-vector rootvec)]) (vector-ref vregs root))) compiled-prog) (define (make-thunk op argidxs regs) (match argidxs ['() op] [(list a) (λ () (op (vector-ref regs a)))] [(list a b) (λ () (op (vector-ref regs a) (vector-ref regs b)))] [(list a b c) (λ () (op (vector-ref regs a) (vector-ref regs b) (vector-ref regs c)))] [(list args ...) (define argc (length args)) (define argv (list->vector args)) (λ () (apply op (for/list ([arg (in-vector argv)]) (vector-ref regs arg))))])) ;; This function: ;; 1) copies only nodes associated with provided brfs - so, gets rid of useless nodes ;; 2) rewrites these nodes as fl-instructions (define (batch-for-compiler batch brfs vars args vregs) (define out (batch-empty)) (define f (batch-recurse batch (λ (brf recurse) (match (deref brf) [(approx _ impl) (recurse impl)] ;; do not push, it is already a batchref [(? symbol? n) (define idx (index-of vars n)) (batch-push! out (make-thunk (λ () (vector-ref args idx)) '() vregs))] [(literal value (app get-representation repr)) (batch-push! out (make-thunk (const (real->repr value repr)) '() vregs))] [(list op args ...) (batch-push! out (make-thunk (impl-info op 'fl) (map (compose batchref-idx recurse) args) vregs))])))) (values out (map f brfs))) ;; Compiles a program of operator implementations into a procedure ;; that evaluates the program on a single input of representation values ;; returning representation values. ;; Translates a Herbie IR into an interpretable IR. ;; Requires some hooks to complete the translation. (define (compile-progs exprs ctx) (define vars (context-vars ctx)) (define-values (batch brfs) (progs->batch exprs #:vars vars)) (compile-batch batch brfs ctx)) (define (compile-batch batch brfs ctx) (define vars (context-vars ctx)) (define args (make-vector (length vars))) (define vregs (make-vector (batch-length batch))) (define-values (batch* brfs*) (batch-for-compiler batch brfs vars args vregs)) (define thunks (batch-get-nodes batch*)) (define rootvec (list->vector (map batchref-idx brfs*))) (timeline-push! 'compiler (batch-tree-size batch* brfs*) (batch-length batch*)) (make-progs-interpreter thunks rootvec args vregs)) ;; Like `compile-progs`, but a single prog. (define (compile-prog expr ctx) (define core (compile-progs (list expr) ctx)) (define (compiled-prog . xs) (vector-ref (apply core xs) 0)) compiled-prog) ================================================ FILE: src/core/derivations.rkt ================================================ #lang racket (require "../core/alternative.rkt" "../syntax/batch.rkt" "programs.rkt" "egg-herbie.rkt" "../config.rkt") (provide add-derivations) (define (canonicalize-proof batch prog-brf proof start-brf) ;; Proofs are on subexpressions; lift to full expression ;; Returns a list of batchrefs instead of expressions (and proof (for/list ([step (in-list proof)]) (define step-brf (batch-add! batch step)) (batch-replace-subexpr batch prog-brf start-brf step-brf)))) ;; Adds proof information to alternatives. ;; start-expr and end-expr are batchrefs (define (add-derivations-to altn) (match altn ; recursive rewrite or simplify, both using egg ; start-brf and end-brf are batchrefs for the subexpressions that were transformed [(alt expr (list 'rr start-brf end-brf (? egg-runner? runner) #f) `(,prev)) (define batch (egg-runner-batch runner)) (define proof (and (not (flag-set? 'generate 'egglog)) (egraph-prove runner start-brf end-brf))) (define proof* (canonicalize-proof batch (alt-expr altn) proof start-brf)) (alt expr `(rr ,start-brf ,end-brf ,runner ,proof*) (list prev))] ; everything else [_ altn])) (define (add-derivations alts) (define cache (make-hash)) (for/list ([altn (in-list alts)]) ;; We need to cache this because we'll see the same alt several times (alt-map (lambda (altn) (hash-ref! cache altn (lambda () (add-derivations-to altn)))) altn))) ================================================ FILE: src/core/egg-herbie.rkt ================================================ #lang racket (require egg-herbie (only-in ffi/vector make-u32vector u32vector-length u32vector-set! u32vector-ref list->u32vector u32vector->list) json) ; for dumping (require "../utils/common.rkt" "../utils/errors.rkt" "../utils/timeline.rkt" "../syntax/platform.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "../syntax/batch.rkt" "programs.rkt" "rules.rkt") (provide (struct-out egg-runner) make-egraph egraph-equal? egraph-prove egraph-best egraph-variations egraph-analyze-rewrite-impact) (module+ test (require rackunit)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; FFI utils (define (u32vector-empty? x) (zero? (u32vector-length x))) (define (in-u32vector vec) (make-do-sequence (lambda () (define len (u32vector-length vec)) (values (lambda (i) (u32vector-ref vec i)) add1 0 (lambda (i) (< i len)) #f #f)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; egg FFI shim ;; ;; egg-herbie requires a bit of nice wrapping ;; - FFIRule: struct defined in egg-herbie ;; - EgraphIter: struct defined in egg-herbie ; Adds expressions returning the root ids (define (egraph-add-exprs ptr batch brfs ctx) ; pre-allocated id vectors for all the common cases (define 0-vec (make-u32vector 0)) (define 1-vec (make-u32vector 1)) (define 2-vec (make-u32vector 2)) (define 3-vec (make-u32vector 3)) (define (list->u32vec xs) (match xs [(list) 0-vec] [(list x) (u32vector-set! 1-vec 0 x) 1-vec] [(list x y) (u32vector-set! 2-vec 0 x) (u32vector-set! 2-vec 1 y) 2-vec] [(list x y z) (u32vector-set! 3-vec 0 x) (u32vector-set! 3-vec 1 y) (u32vector-set! 3-vec 2 z) 3-vec] [_ (list->u32vector xs)])) ; node -> natural ; inserts an expression into the e-graph, returning its e-class id. (define (insert-node! node) (match node [(list op ids ...) (egraph_add_node ptr (~s op) (list->u32vec ids))] [(? (disjoin symbol? number?) x) (egraph_add_node ptr (~s x) 0-vec)])) (define reprs (batch-reprs batch ctx)) (define add-to-egraph (batch-recurse batch (λ (brf recurse) (define node (deref brf)) (match node [(literal v _) (insert-node! v)] [(? number?) (insert-node! node)] [(? symbol?) (insert-node! (var->egg-var node ctx))] [(hole prec spec) (recurse spec)] ; "hole" terms currently disappear [(approx spec impl) (insert-node! (list '$approx (recurse spec) (recurse impl)))] [(list op (app recurse args) ...) (insert-node! (cons op args))])))) (for/list ([brf (in-list brfs)]) (define brf-id (add-to-egraph brf)) ; remapping of brf (egraph_add_root ptr brf-id) brf-id)) ;; runs rules on an egraph (optional iteration limit) (define (egraph-run ptr ffi-rules node-limit iter-limit scheduler) (define u32_max 4294967295) ; since we can't send option types (define node_limit (if node-limit node-limit u32_max)) (define iter_limit (if iter-limit iter-limit u32_max)) (define simple_scheduler? (match scheduler ['backoff #f] ['simple #t] [_ (error 'egraph-run "unknown scheduler: `~a`" scheduler)])) (egraph_run ptr ffi-rules iter_limit node_limit simple_scheduler?)) (define (egraph-get-simplest ptr node-id iteration ctx) (define expr (egraph_get_simplest ptr node-id iteration)) (egg-expr->expr expr ctx)) (define (egraph-get-variants ptr node-id orig-expr ctx) (define egg-expr (expr->egg-expr orig-expr ctx)) (define exprs (egraph_get_variants ptr node-id egg-expr)) (for/list ([expr (in-list exprs)]) (egg-expr->expr expr ctx))) (define empty-u32vec (make-u32vector 0)) ;; Extracts the nodes of an e-class as a vector ;; where each enode is either a symbol, number, or list (define (egraph-get-eclass ptr id) (define eclass (egraph_get_eclass ptr id)) ; need to fix up any constant operators (for ([enode (in-vector eclass)] [i (in-naturals)] #:when (and (symbol? enode) (not (string-prefix? (symbol->string enode) "$var")))) (vector-set! eclass i (cons enode empty-u32vec))) eclass) (define (egraph-expr-equal? ptr expr goal ctx) (define-values (batch brfs) (progs->batch (list expr goal))) (match-define (list id1 id2) (egraph-add-exprs ptr batch brfs ctx)) (= id1 id2)) ;; returns a flattened list of terms or #f if it failed to expand the proof due to budget (define (egraph-get-proof ptr expr goal ctx) (define egg-expr (expr->egg-expr expr ctx)) (define egg-goal (expr->egg-expr goal ctx)) (define str (egraph_get_proof ptr egg-expr egg-goal)) (cond [(<= (string-length str) (*proof-max-string-length*)) (define converted (for/list ([expr (in-port read (open-input-string str))]) (egg-expr->expr expr ctx))) (define expanded (expand-proof converted (box (*proof-max-length*)))) (if (member #f expanded) #f expanded)] [else #f])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; eggIR ;; ;; eggIR is an S-expr language nearly identical to Herbie's various IRs ;; consisting of two variants: ;; - patterns: all variables are prefixed by '?' ;; - expressions: all variables are normalized into `h` where is an integer ;; ;; Translates a Herbie rule LHS or RHS into a pattern usable by egg. ;; Rules can be over specs or impls. (define (expr->egg-pattern expr) (let loop ([expr expr]) (match expr [(? number?) expr] [(? literal?) (literal-value expr)] [(? symbol?) (string->symbol (format "?~a" expr))] [(approx spec impl) (list '$approx (loop spec) (loop impl))] [(list op args ...) (cons op (map loop args))]))) (define (var->egg-var var ctx) (define idx (index-of (context-vars ctx) var)) (string->symbol (format "$var~a" idx))) (define (egg-var->var egg-var ctx) (define idx (string->number (substring (symbol->string egg-var) 4))) (list-ref (context-vars ctx) idx)) ;; Translates a Herbie expression into an expression usable by egg. ;; Updates translation dictionary upon encountering variables. ;; Result is the expression. (define (expr->egg-expr expr ctx) (let loop ([expr expr]) (match expr [(? number?) expr] [(? literal?) (literal-value expr)] [(? symbol? x) (var->egg-var x ctx)] [(approx spec impl) (list '$approx (loop spec) (loop impl))] [(hole precision spec) (loop spec)] [(list op args ...) (cons op (map loop args))]))) (define (flatten-let expr) (let loop ([expr expr] [env (hash)]) (match expr [(? number?) expr] [(? symbol?) (hash-ref env expr expr)] [`(let (,var ,term) ,body) (loop body (hash-set env var (loop term env)))] [`(,op ,args ...) (cons op (map (curryr loop env) args))]))) ;; Converts an S-expr from egg into one Herbie understands ;; TODO: typing information is confusing since proofs mean ;; we may process mixed spec/impl expressions; ;; only need `type` to correctly interpret numbers (define (egg-parsed->expr expr ctx type) (let loop ([expr expr] [type type]) (match expr [(? number?) (if (representation? type) (literal expr (representation-name type)) expr)] [(? symbol?) (if (string-prefix? (symbol->string expr) "$var") (egg-var->var expr ctx) (list expr))] [(list '$approx spec impl) ; approx (define spec-type (if (representation? type) (representation-type type) type)) (approx (loop spec spec-type) (loop impl type))] [`(Explanation ,body ...) `(Explanation ,@(map (lambda (e) (loop e type)) body))] [(list 'Rewrite=> rule expr) (list 'Rewrite=> rule (loop expr type))] [(list 'Rewrite<= rule expr) (list 'Rewrite<= rule (loop expr type))] [(list op args ...) #:when (string-prefix? (symbol->string op) "sound-") (define op* (string->symbol (substring (symbol->string op) (string-length "sound-")))) (define args* (drop-right args 1)) (cons op* (map loop args* (map (const 'real) args*)))] [(list op args ...) ;; Unfortunately the type parameter doesn't tell us much because mixed exprs exist ;; so if we see something like (and a b) we literally don't know which "and" it is (cons op (map loop args (cond [(and (operator-exists? op) (impl-exists? op)) (if (representation? type) (impl-info op 'itype) (operator-info op 'itype))] [(impl-exists? op) (impl-info op 'itype)] [(operator-exists? op) (operator-info op 'itype)])))]))) ;; Parses a string from egg into a single S-expr. (define (egg-expr->expr egg-expr ctx) (egg-parsed->expr (flatten-let egg-expr) ctx (context-repr ctx))) (module+ test (require "../syntax/float.rkt" "../syntax/load-platform.rkt") (activate-platform! (*platform-name*)) (define ctx (context '(x y z) (make-list 3 ))) (define test-exprs (list (cons '(+.f64 y x) '(+.f64 $var1 $var0)) (cons '(+.f64 x y) '(+.f64 $var0 $var1)) (cons '(-.f64 #s(literal 2 binary64) (+.f64 x y)) '(-.f64 2 (+.f64 $var0 $var1))) (cons '(-.f64 z (+.f64 (+.f64 y #s(literal 2 binary64)) x)) '(-.f64 $var2 (+.f64 (+.f64 $var1 2) $var0))) (cons '(*.f64 x y) '(*.f64 $var0 $var1)) (cons '(+.f64 (*.f64 x y) #s(literal 2 binary64)) '(+.f64 (*.f64 $var0 $var1) 2)) (cons '(cos.f32 (PI.f32)) '(cos.f32 (PI.f32))) (cons '(if.f64 (TRUE) x y) '(if.f64 (TRUE) $var0 $var1)))) (let ([egg-graph (egraph_create)]) (for ([(in expected-out) (in-dict test-exprs)]) (define out (expr->egg-expr in ctx)) (define computed-in (egg-expr->expr out ctx)) (check-equal? out expected-out) (check-equal? computed-in in))) (check-equal? (egg-expr->expr '(sound-sqrt $var0 $var1) ctx) '(sqrt x)) (set! ctx (context '(x a b c r) (make-list 5 ))) (define extended-expr-list ; specifications (list '(/ (- (exp x) (exp (neg x))) 2) '(/ (+ (neg b) (sqrt (- (* b b) (* (* 3 a) c)))) (* 3 a)) '(/ (+ (neg b) (sqrt (- (* b b) (* (* 3 a) c)))) (* 3 a)) '(* r 30) '(* 23/54 r) '(+ 3/2 14/10) ; implementations `(/.f64 (-.f64 (exp.f64 x) (exp.f64 (neg.f64 x))) ,(literal 2 'binary64)) `(/.f64 (+.f64 (neg.f64 b) (sqrt.f64 (-.f64 (*.f64 b b) (*.f64 (*.f64 ,(literal 3 'binary64) a) c)))) (*.f64 ,(literal 3 'binary64) a)) `(/.f64 (+.f64 (neg.f64 b) (sqrt.f64 (-.f64 (*.f64 b b) (*.f64 (*.f64 ,(literal 3 'binary64) a) c)))) (*.f64 ,(literal 3 'binary64) a)) `(*.f64 r ,(literal 30 'binary64)) `(*.f64 ,(literal 23/54 'binary64) r) `(+.f64 ,(literal 3/2 'binary64) ,(literal 14/10 'binary64)))) (let ([egg-graph (egraph_create)]) (for ([expr extended-expr-list]) (define egg-expr (expr->egg-expr expr ctx)) (check-equal? (egg-expr->expr egg-expr ctx) expr)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Proofs ;; ;; Proofs from egg contain let expressions (not Scheme like) as ;; well as other information about rewrites; proof extraction requires ;; some flattening and translation (define (remove-rewrites proof) (match proof [`(Rewrite=> ,_ ,something) (remove-rewrites something)] [`(Rewrite<= ,_ ,something) (remove-rewrites something)] [(list _ ...) (map remove-rewrites proof)] [_ proof])) ;; Performs a product, but traverses the elements in order ;; This is the core logic of flattening a proof given flattened proofs for each child of a node (define (sequential-product elements) (cond [(empty? elements) (list empty)] [else (define without-rewrites (remove-rewrites (last (first elements)))) (append (for/list ([head (first elements)]) (cons head (map first (rest elements)))) (for/list ([other (in-list (rest (sequential-product (rest elements))))]) (cons without-rewrites other)))])) ;; returns a flattened list of terms ;; The first term has no rewrite- the rest have exactly one rewrite (define (expand-proof-term term budget) (let loop ([term term]) (cond [(<= (unbox budget) 0) (list #f)] [else (match term [(? symbol?) (list term)] [(? literal?) (list term)] [(? number?) (list term)] [(approx spec impl) (define children (list (loop spec) (loop impl))) (cond [(member (list #f) children) (list #f)] [else (define res (sequential-product children)) (set-box! budget (- (unbox budget) (length res))) (map (curry apply approx) res)])] [`(Explanation ,body ...) (expand-proof body budget)] [(? list?) (define children (map loop term)) (cond [(member (list #f) children) (list #f)] [else (define res (sequential-product children)) (set-box! budget (- (unbox budget) (length res))) res])] [_ (error "Unknown proof term ~a" term)])]))) ;; Remove the front term if it doesn't have any rewrites (define (remove-front-term proof) (if (equal? (remove-rewrites (first proof)) (first proof)) (rest proof) proof)) ;; converts a let-bound tree explanation ;; into a flattened proof for use by Herbie (define (expand-proof proof budget) (define expanded (map (curryr expand-proof-term budget) proof)) ;; get rid of any unnecessary terms (define contiguous (cons (first expanded) (map remove-front-term (rest expanded)))) ;; append together the proofs (define res (apply append contiguous)) (set-box! budget (- (unbox budget) (length proof))) (if (member #f res) (list #f) res)) (module+ test (check-equal? (sequential-product `((1 2) (3 4 5) (6))) `((1 3 6) (2 3 6) (2 4 6) (2 5 6))) (check-equal? (expand-proof-term '(Explanation (+ x y) (+ y x)) (box 10)) '((+ x y)))) ;; egg rule cache: rule -> FFI-rule (define/reset *egg-rule-cache* (make-hasheq)) ;; Expand and convert the rules for egg. ;; Uses a cache to only expand each rule once. (define (convert-rules rules) (for/list ([ru (in-list rules)]) (hash-ref! (*egg-rule-cache*) ru (lambda () (define input (expr->egg-pattern (rule-input ru))) (define output (expr->egg-pattern (rule-output ru))) (make-ffi-rule (rule-name ru) input output))))) ;; Rules from impl to spec (fixed for a particular platform) (define/reset *lifting-rules* (make-hash)) ;; Rules from spec to impl (fixed for a particular platform) (define/reset *lowering-rules* (make-hash)) ;; Synthesizes the LHS and RHS of lifting/lowering rules. (define (impl->rule-parts impl) (define vars (impl-info impl 'vars)) (define spec (impl-info impl 'spec)) (values vars spec (cons impl vars))) ;; Synthesizes lifting rules for a platform platform. (define (platform-lifting-rules [pform (*active-platform*)]) (define impls (platform-impls pform)) (for/list ([impl (in-list impls)]) (hash-ref! (*lifting-rules*) (cons impl pform) (lambda () (define name (sym-append 'lift- impl)) (define-values (vars spec-expr impl-expr) (impl->rule-parts impl)) (rule name impl-expr spec-expr '(lifting)))))) ;; Synthesizes lowering rules for a given platform. (define (platform-lowering-rules [pform (*active-platform*)]) (define impls (platform-impls pform)) (append* (for/list ([impl (in-list impls)]) (hash-ref! (*lowering-rules*) (cons impl pform) (lambda () (define name (sym-append 'lower- impl)) (define-values (vars spec-expr impl-expr) (impl->rule-parts impl)) (list (rule name spec-expr impl-expr '(lowering)))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Racket egraph ;; ;; Racket representation of a typed egraph. ;; Given an e-graph from egg-herbie, we can split every e-class ;; by type ensuring that every term in an e-class has the same output type. ;; This trick makes extraction easier. ;; - eclasses: vector of enodes ;; - types: vector-map from e-class to type/representation ;; - leaf?: vector-map from e-class to boolean indicating if it contains a leaf node ;; - constants: vector-map from e-class to a number or #f ;; - parents: vector-map from e-class to its parent e-classes (as a vector) ;; - canon: map from (Rust) e-class, type to (Racket) e-class ;; - ctx: the standard variable context (struct regraph (eclasses types leaf? constants parents canon ctx)) ;; Returns all representatations (and their types) in the current platform. (define (all-reprs/types [pform (*active-platform*)]) (remove-duplicates (cons 'array (append-map (lambda (repr) (list repr (representation-type repr))) (platform-reprs pform))))) ;; Returns the type(s) of an enode so it can be placed in the proper e-class. ;; Typing rules: ;; - numbers: every real representation (or real type) ;; - variables: lookup in the context ;; - `if`: type is every representation (or type) [can prune incorrect ones] ;; - `approx`: every real representation [can prune incorrect ones] ;; - ops/impls: its output type/representation ;; NOTE: we can constrain "every" type by using the platform. (define (enode-type enode ctx) (match enode [(? number?) (cons 'real (platform-reprs (*active-platform*)))] ; number [(? symbol?) ; variable (define var (egg-var->var enode ctx)) (define repr (context-lookup ctx var)) (list repr (representation-type repr))] [(cons f _) ; application (cond [(eq? f '$approx) (platform-reprs (*active-platform*))] [(string-prefix? (symbol->string f) "sound-") (list 'real)] [else (filter values (list (and (impl-exists? f) (impl-info f 'otype)) (and (operator-exists? f) (operator-info f 'otype))))])])) ;; Rebuilds an e-node using typed e-classes (define (rebuild-enode enode type lookup) (match enode [(? number?) enode] ; number [(? symbol?) enode] ; variable [(cons f ids) ; application (cond [(eq? f '$approx) ; approx node (define spec (u32vector-ref ids 0)) (define impl (u32vector-ref ids 1)) (list '$approx (lookup spec (representation-type type)) (lookup impl type))] [(string-prefix? (~a f) "sound-") (define op (string->symbol (substring (symbol->string f) (string-length "sound-")))) (list* op (map (λ (x) (lookup (u32vector-ref ids x) 'real)) (range (- (u32vector-length ids) 1))))] [else (define itypes (cond [(representation? type) (impl-info f 'itype)] [else (operator-info f 'itype)])) ; unsafe since we don't check that |itypes| = |ids| ; optimize for common cases to avoid extra allocations (cons f (match itypes [(list) '()] [(list t1) (list (lookup (u32vector-ref ids 0) t1))] [(list t1 t2) (list (lookup (u32vector-ref ids 0) t1) (lookup (u32vector-ref ids 1) t2))] [(list t1 t2 t3) (list (lookup (u32vector-ref ids 0) t1) (lookup (u32vector-ref ids 1) t2) (lookup (u32vector-ref ids 2) t3))] [_ (map lookup (u32vector->list ids) itypes)]))])])) ;; Splits untyped eclasses into typed eclasses. ;; Nodes are duplicated across their possible types. (define (split-untyped-eclasses ptr ctx) (define eclass-ids (egraph_get_eclasses ptr)) (define max-id (for/fold ([current-max 0]) ([egg-id (in-u32vector eclass-ids)]) (max current-max egg-id))) (define egg-id->idx (make-u32vector (+ max-id 1))) (for ([egg-id (in-u32vector eclass-ids)] [idx (in-naturals)]) (u32vector-set! egg-id->idx egg-id idx)) (define types (all-reprs/types)) (define type->idx (make-hash)) (for ([type (in-list types)] [idx (in-naturals)]) (hash-set! type->idx type idx)) (define num-types (hash-count type->idx)) ; maps (idx, type) to type eclass id (define (idx+type->id idx type) (+ (* idx num-types) (hash-ref type->idx type))) ; maps (untyped eclass id, type) to typed eclass id (define (lookup-id eid type) (idx+type->id (u32vector-ref egg-id->idx eid) type)) ; allocate enough eclasses for every (egg-id, type) combination (define n (* (u32vector-length eclass-ids) num-types)) (define id->eclass (make-vector n '())) (define id->parents (make-vector n '())) (define id->leaf? (make-vector n #f)) ; for each eclass, extract the enodes ; ::= ; | ; | ( . ) ; NOTE: nodes in typed eclasses are reversed relative ; to their position in untyped eclasses (for ([eid (in-u32vector eclass-ids)] [idx (in-naturals)]) (define enodes (egraph-get-eclass ptr eid)) (for ([enode (in-vector enodes)]) ; get all possible types for the enode ; lookup its correct eclass and add the rebuilt node (define types (enode-type enode ctx)) (for ([type (in-list types)]) (define id (idx+type->id idx type)) (define enode* (rebuild-enode enode type lookup-id)) (vector-set! id->eclass id (cons enode* (vector-ref id->eclass id))) (match enode* [(list _ ids ...) #:when (null? ids) (vector-set! id->leaf? id #t)] [(list _ ids ...) (for ([child-id (in-list ids)]) (vector-set! id->parents child-id (cons id (vector-ref id->parents child-id))))] [(? symbol?) (vector-set! id->leaf? id #t)] [(? number?) (vector-set! id->leaf? id #t)])))) ; dedup `id->parents` values (for ([id (in-range n)]) (vector-set! id->parents id (list->vector (remove-duplicates (vector-ref id->parents id))))) (values id->eclass id->parents id->leaf? eclass-ids egg-id->idx type->idx)) ;; TODO: reachable from roots? ;; Prunes e-nodes that are not well-typed. ;; An e-class is well-typed if it has one well-typed node ;; A node is well-typed if all of its child e-classes are well-typed. (define (prune-ill-typed! id->eclass id->parents id->leaf?) (define n (vector-length id->eclass)) ;; is the e-class well-typed? (define typed?-vec (make-vector n #f)) (define (eclass-well-typed? id) (vector-ref typed?-vec id)) ;; is the e-node well-typed? (define (enode-typed? enode) (or (number? enode) (symbol? enode) (and (list? enode) (andmap eclass-well-typed? (cdr enode))))) ; mark all well-typed e-classes and prune nodes that are not well-typed (define (check-typed! dirty?-vec) (define rerun? #f) (for ([id (in-range n)] [dirty? (in-vector dirty?-vec)] [typed? (in-vector typed?-vec)] [eclass (in-vector id->eclass)] [parent-ids (in-vector id->parents)] #:when dirty?) (vector-set! dirty?-vec id #f) (when (and (not typed?) (ormap enode-typed? eclass)) (vector-set! typed?-vec id #t) (for ([parent-id (in-vector parent-ids)]) (vector-set! dirty?-vec parent-id #t) (when (< parent-id id) (set! rerun? #t))))) (when rerun? (check-typed! dirty?-vec))) (check-typed! (vector-copy id->leaf?)) (for ([id (in-range n)]) (define eclass (vector-ref id->eclass id)) (vector-set! id->eclass id (filter enode-typed? eclass))) ; sanity check: every child id points to a non-empty e-class (for ([id (in-range n)]) (define eclass (vector-ref id->eclass id)) (for ([enode (in-list eclass)]) (match enode [(list _ ids ...) (for ([id (in-list ids)] #:when (null? (vector-ref id->eclass id))) (error 'prune-ill-typed! "eclass ~a is empty, eclasses ~a" id (for/vector #:length n ([id (in-range n)]) (list id (vector-ref id->eclass id)))))] [_ (void)])))) ;; Rebuilds eclasses and associated data after pruning. (define (rebuild-eclasses id->eclass eclass-ids egg-id->idx type->idx) (define n (vector-length id->eclass)) (define remap (make-vector n #f)) ; build the id map (define n* 0) (for ([id (in-range n)]) (define eclass (vector-ref id->eclass id)) (unless (null? eclass) (vector-set! remap id n*) (set! n* (add1 n*)))) ; invert `type->idx` map (define idx->type (make-hash)) (define num-types (hash-count type->idx)) (for ([(type idx) (in-hash type->idx)]) (hash-set! idx->type idx type)) ; rebuild eclass and type vectors ; transform each eclass from a list to a vector (define eclasses (make-vector n* #f)) (define types (make-vector n* #f)) (for ([id (in-range n)]) (define id* (vector-ref remap id)) (when id* (define eclass (vector-ref id->eclass id)) (vector-set! eclasses id* (for/vector #:length (length eclass) ([enode (in-list eclass)]) (match enode [(? number?) enode] [(? symbol?) enode] [(list op ids ...) (define ids* (map (lambda (id) (vector-ref remap id)) ids)) (cons op ids*)]))) (vector-set! types id* (hash-ref idx->type (modulo id num-types))))) ; build the canonical id map (define egg-id->id (make-hash)) (for ([eid (in-u32vector eclass-ids)]) (define idx (u32vector-ref egg-id->idx eid)) (define id0 (* idx num-types)) (for ([id (in-range id0 (+ id0 num-types))]) (define id* (vector-ref remap id)) (when id* (define type (vector-ref types id*)) (hash-set! egg-id->id (cons eid type) id*)))) (values eclasses types egg-id->id)) ;; Splits untyped eclasses into typed eclasses, ;; keeping only the subset of enodes that are well-typed. (define (make-typed-eclasses ptr ctx) ;; Step 1: split Rust-eclasses by type (define-values (id->eclass id->parents id->leaf? eclass-ids egg-id->idx type->idx) (split-untyped-eclasses ptr ctx)) ;; Step 2: keep well-typed e-nodes ;; An e-class is well-typed if it has one well-typed node ;; A node is well-typed if all of its child e-classes are well-typed. (prune-ill-typed! id->eclass id->parents id->leaf?) ;; Step 3: remap e-classes ;; Any empty e-classes must be removed, so we re-map every id (rebuild-eclasses id->eclass eclass-ids egg-id->idx type->idx)) ;; Analyzes eclasses for their properties. ;; The result are vector-maps from e-class ids to data. ;; - parents: parent e-classes (as a vector) ;; - leaf?: does the e-class contain a leaf node ;; - constants: the e-class constant (if one exists) (define (analyze-eclasses eclasses) (define n (vector-length eclasses)) (define parents (make-vector n '())) (define leaf? (make-vector n '#f)) (define constants (make-vector n #f)) (for ([id (in-range n)]) (define eclass (vector-ref eclasses id)) (for ([enode eclass]) ; might be a list or vector (match enode [(? number? n) (vector-set! leaf? id #t) (vector-set! constants id n)] [(? symbol?) (vector-set! leaf? id #t)] [(list _ ids ...) (when (null? ids) (vector-set! leaf? id #t)) (for ([child-id (in-list ids)]) (vector-set! parents child-id (cons id (vector-ref parents child-id))))]))) ; parent map: remove duplicates, convert lists to vectors (for ([id (in-range n)]) (define ids (remove-duplicates (vector-ref parents id))) (vector-set! parents id (list->vector ids))) (values parents leaf? constants)) ;; Constructs a Racket egraph from an S-expr representation of ;; an egraph and data to translate egg IR to herbie IR. (define (make-regraph ptr ctx) ;; split the e-classes by type (define-values (eclasses types canon) (make-typed-eclasses ptr ctx)) ;; analyze each eclass (define-values (parents leaf? constants) (analyze-eclasses eclasses)) ; construct the `regraph` instance (regraph eclasses types leaf? constants parents canon ctx)) (define (regraph-nodes->json regraph) (define cost (platform-node-cost-proc (*active-platform*))) (for/hash ([n (in-naturals)] [eclass (in-vector (regraph-eclasses regraph))] #:when true [k (in-naturals)] [enode eclass]) (define type (vector-ref (regraph-types regraph) n)) (define cost (if (representation? type) (match enode [(? number?) (platform-repr-cost (*active-platform*) type)] [(? symbol?) (platform-repr-cost (*active-platform*) type)] [(list '$approx x y) 0] [(list op args ...) (impl-info op 'cost)]) 1)) (values (string->symbol (format "~a.~a" n k)) (hash 'op (~a (if (list? enode) (car enode) enode)) 'children (if (list? enode) (map (lambda (e) (format "~a.0" e)) (cdr enode)) '()) 'eclass (~a n) 'cost cost)))) ;; Egraph node has children. ;; Nullary operators have no children! (define (node-has-children? node) (and (pair? node) (pair? (cdr node)))) ;; Computes an analysis for each eclass. ;; Takes a regraph and an procedure taking the analysis, an eclass, and ;; its eclass id producing a non-`#f` result when the parents of the eclass ;; need to be revisited. Result is a vector where each entry is ;; the eclass's analysis. (define (regraph-analyze regraph eclass-proc #:analysis [analysis #f]) (define eclasses (regraph-eclasses regraph)) (define leaf? (regraph-leaf? regraph)) (define parents (regraph-parents regraph)) (define n (vector-length eclasses)) ; set analysis if not provided (unless analysis (set! analysis (make-vector n #f))) (define dirty?-vec (vector-copy leaf?)) ; visit eclass on next pass? (define changed?-vec0 (make-vector n #f)) ; eclass was changed last iteration (define changed?-vec*0 (make-vector n #f)) ; eclass changed this iteration ; run the analysis (let sweep! ([iter 0] [changed?-vec changed?-vec0] [changed?-vec* changed?-vec*0]) (define rerun? #f) (for ([id (in-range n)] [dirty-this? (in-vector dirty?-vec)] [eclass (in-vector eclasses)] [parent-ids (in-vector parents)] #:when dirty-this?) (vector-set! dirty?-vec id #f) (when (eclass-proc analysis changed?-vec iter eclass id) ; eclass analysis was updated: need to revisit the parents ; expose updates to later eclasses in this iteration (vector-set! changed?-vec id #t) (vector-set! changed?-vec* id #t) (for ([parent-id (in-vector parent-ids)]) (vector-set! dirty?-vec parent-id #t) (when (<= parent-id id) (set! rerun? #t))))) ; if rerun, analysis has not converged so loop (when rerun? (vector-fill! changed?-vec #f) (sweep! (add1 iter) changed?-vec* changed?-vec))) ; Invariant: all eclasses have an analysis (for ([id (in-range n)] [eclass-analysis (in-vector analysis)] #:unless eclass-analysis) (define types (regraph-types regraph)) (error 'regraph-analyze "analysis not run on all eclasses: ~a ~a" eclass-proc (for/vector #:length n ([id (in-range n)] [type (in-vector types)] [eclass (in-vector eclasses)] [eclass-analysis (in-vector analysis)]) (list id type eclass eclass-analysis)))) analysis) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Regraph typed extraction ;; ;; Typed extraction is ideal for extracting specifications from an egraph. ;; By "typed", we refer to the output "representation" of a given operator. ;; This style of extractor associates to each eclass the best ;; (cost, node) pair for each possible output type in the eclass. ;; The extractor procedure takes an eclass id and an output type. ;; ;; Typed cost functions take: ;; - the regraph we are extracting from ;; - a mutable cache (to possibly stash per-node data) ;; - the node we are computing cost for ;; - 3 argument procedure taking: ;; - an eclass id ;; - an output type ;; - a default failure value ;; ;; The typed extraction algorithm. ;; Extraction is partial, that is, the result of the extraction ;; procedure is `#f` if extraction finds no well-typed program ;; at a particular id with a particular output type. (define ((typed-egg-batch-extractor batch-extract-to) regraph) (define eclasses (regraph-eclasses regraph)) (define types (regraph-types regraph)) (define n (vector-length eclasses)) ; e-class costs (define costs (make-vector n #f)) ; looks up the cost (define (unsafe-eclass-cost id) (car (vector-ref costs id))) ; do its children e-classes have a cost (define (node-ready? node) (match node [(? number?) #t] [(? symbol?) #t] [(list '$approx _ impl) (vector-ref costs impl)] [(list _ ids ...) (andmap (lambda (id) (vector-ref costs id)) ids)])) ; computes cost of a node (as long as each of its children have costs) ; cost function has access to a mutable value through `cache` (define cache (box #f)) (define (node-cost node type) (and (node-ready? node) (platform-egg-cost-proc regraph cache node type unsafe-eclass-cost))) ; updates the cost of the current eclass. ; returns whether the cost of the current eclass has improved. (define (eclass-set-cost! _ changed?-vec iter eclass id) (define type (vector-ref types id)) (define updated? #f) ; update cost information (define (update-cost! new-cost node) (when new-cost (define prev-cost&node (vector-ref costs id)) (when (or (not prev-cost&node) ; first cost (< new-cost (car prev-cost&node))) ; better cost (vector-set! costs id (cons new-cost node)) (set! updated? #t)))) ; optimization: we only need to update node cost as needed. ; (i) terminals, nullary operators: only compute once ; (ii) non-nullary operators: compute when any of its child eclasses ; have their analysis updated (define (node-requires-update? node) (if (node-has-children? node) (ormap (lambda (id) (vector-ref changed?-vec id)) (cdr node)) (= iter 0))) ; iterate over each node (for ([node (in-vector eclass)] #:when (node-requires-update? node)) (define new-cost (node-cost node type)) (update-cost! new-cost node)) updated?) ; run the analysis (regraph-analyze regraph eclass-set-cost! #:analysis costs) (define ctx (regraph-ctx regraph)) (define-values (add-id add-enode) (egg-nodes->batch costs batch-extract-to ctx)) ;; These functions provide a setup to extract nodes into batch-extract-to from nodes (list add-id add-enode)) (define (egg-nodes->batch egg-nodes batch ctx) (define (eggref id) (cdr (vector-ref egg-nodes id))) (define memo (make-hash)) (define (add-enode enode type) (define enode* (match enode [(? number?) (if (representation? type) (literal enode (representation-name type)) enode)] [(? symbol?) (if (string-prefix? (symbol->string enode) "$var") (egg-var->var enode ctx) enode)] [(list '$approx spec impl) (define spec-type (if (representation? type) (representation-type type) type)) (approx (batchref-idx (add-id spec spec-type)) (batchref-idx (add-id impl type)))] [(list impl args ...) (define args* (for/list ([arg-id (in-list args)] [arg-type (in-list (if (representation? type) (impl-info impl 'itype) (operator-info impl 'itype)))]) (batchref-idx (add-id arg-id arg-type)))) (cons impl args*)])) (batchref-idx (batch-push! batch enode*))) (define (add-id id type) (define key (cons id type)) (define idx (hash-ref! memo key (λ () (add-enode (eggref id) type)))) (batchref batch idx)) (values add-id (λ (enode type) (batchref batch (add-enode enode type))))) ;; Is fractional with odd denominator. (define (fraction-with-odd-denominator? frac) (cond [(rational? frac) (define denom (denominator frac)) (and (> denom 1) (odd? denom))] [else #f])) ;; Decompose an e-node representing an impl of `(pow b e)`. ;; Returns either `#f` or the `(cons b e)` (define (pow-impl-args impl args) (define vars (impl-info impl 'vars)) (match (impl-info impl 'spec) [(list 'pow b e) #:when (set-member? vars e) (define env (map cons vars args)) (define b* (dict-ref env b b)) (define e* (dict-ref env e e)) (cons b* e*)] [_ #f])) ;; Old cost model version (define (default-egg-cost-proc regraph cache node type rec) (match node [(? number?) 1] [(? symbol?) 1] ; approx node [(list '$approx _ impl) (rec impl)] [(list (? impl-exists? impl) args ...) (match (pow-impl-args impl args) [(cons _ e) #:when (let ([n (vector-ref (regraph-constants regraph) e)]) (fraction-with-odd-denominator? n)) +inf.0] [_ (apply + 1 (map rec args))])] [(list 'pow b e) (define n (vector-ref (regraph-constants regraph) e)) (if (fraction-with-odd-denominator? n) +inf.0 (+ 1 (rec b) (rec e)))] [(list _ args ...) (apply + 1 (map rec args))])) ;; Per-node cost function according to the platform ;; `rec` takes an id, type, and failure value (define (platform-egg-cost-proc regraph cache node type rec) (cond [(representation? type) (define ctx (regraph-ctx regraph)) (define node-cost-proc (platform-node-cost-proc (*active-platform*))) (match node ; numbers (repr is unused) [(? number? n) ((node-cost-proc (literal n type) type))] [(? symbol?) ; variables (define repr (context-lookup ctx (egg-var->var node ctx))) ((node-cost-proc node repr))] ; approx node [(list '$approx _ impl) (rec impl)] [(list (? impl-exists?) args ...) ; impls (define cost-proc (node-cost-proc node type)) (apply cost-proc (map rec args))])] [else (default-egg-cost-proc regraph cache node type rec)])) ;; Extracts the best expression according to the extractor. ;; Result is a single element list. (define (regraph-extract-best regraph extract id type) (define canon (regraph-canon regraph)) ; Extract functions to extract exprs from egraph (match-define (list extract-id _) extract) ; extract expr (define key (cons id type)) (define id* (hash-ref canon key #f)) (cond ; at least one extractable expression [id* (list (extract-id id* type))] ; no extractable expressions [else (list)])) ;; Extracts multiple expressions according to the extractor (define (regraph-extract-variants regraph extract id type) ; regraph fields (define eclasses (regraph-eclasses regraph)) (define canon (regraph-canon regraph)) ; Functions for egg-extraction (match-define (list _ extract-enode) extract) ; extract expressions (define key (cons id type)) (cond ; at least one extractable expression [(hash-has-key? canon key) (define id* (hash-ref canon key)) (remove-duplicates (for/list ([enode (vector-ref eclasses id*)]) (extract-enode enode type)) #:key batchref-idx)] [else (list)])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Scheduler ;; ;; A mini-interpreter for egraph "schedules" including running egg, ;; pruning certain kinds of nodes, extracting expressions, etc. ;; Runs rules over the egraph with the given egg parameters. (define (egraph-run-rules egg-graph0 egg-rules #:node-limit [node-limit #f] #:iter-limit [iter-limit #f] #:scheduler [scheduler 'backoff]) ;; run the rules (define egg-graph (egraph_copy egg-graph0)) (define iteration-data (egraph-run egg-graph egg-rules node-limit iter-limit scheduler)) (when (egraph_is_unsound_detected egg-graph) (warn 'unsound-egraph #:url "faq.html#unsound-egraph" "unsoundness detected in the egraph")) (timeline-push! 'stop (~a (egraph_get_stop_reason egg-graph)) 1) (values egg-graph iteration-data)) (define (egraph-analyze-rewrite-impact batch brfs ctx iter) (define egg-graph (egraph_create)) (egraph-add-exprs egg-graph batch brfs ctx) (define lifting-rules (convert-rules (platform-lifting-rules))) (define-values (egg-graph1 _1) (egraph-run-rules egg-graph lifting-rules #:iter-limit 1 #:scheduler 'simple)) (define-values (egg-graph2 iter-data2) (if (> iter 0) (egraph-run-rules egg-graph1 (convert-rules (*rules*)) #:iter-limit iter) (values egg-graph1 _1))) (define-values (egg-graph3 iter-data3) (egraph-run-rules egg-graph2 '())) (define initial-size (iteration-data-num-nodes (last iter-data3))) (define results (for/list ([rule (in-list (*rules*))]) (define-values (egg-graph5 iter-data5) (egraph-run-rules egg-graph3 (convert-rules (list rule)) #:iter-limit 2)) (define size (iteration-data-num-nodes (last (if (empty? iter-data5) iter-data3 iter-data5)))) (cons rule (- size initial-size)))) (define final-size (let-values ([(egg-graph6 iter-data6) (egraph-run-rules egg-graph3 (convert-rules (*rules*)) #:iter-limit 2)]) (iteration-data-num-nodes (last (if (empty? iter-data6) iter-data3 iter-data6))))) (values initial-size final-size results)) (define (egraph-run-schedule batch brfs schedule ctx) ; allocate the e-graph (define egg-graph (egraph_create)) ; insert expressions into the e-graph (define root-ids (egraph-add-exprs egg-graph batch brfs ctx)) ; run the schedule (define egg-graph* (for/fold ([egg-graph egg-graph]) ([step (in-list schedule)]) (define-values (egg-graph* iteration-data) (match step ['lift (define rules (convert-rules (platform-lifting-rules))) (egraph-run-rules egg-graph rules #:iter-limit 1 #:scheduler 'simple)] ['lower (define rules (convert-rules (platform-lowering-rules))) (egraph-run-rules egg-graph rules #:iter-limit 1 #:scheduler 'simple)] ['unsound (define rules (convert-rules (*sound-removal-rules*))) (egraph-run-rules egg-graph rules #:iter-limit 1 #:scheduler 'simple)] ['rewrite (define rules (convert-rules (*rules*))) (egraph-run-rules egg-graph rules #:node-limit (*node-limit*))])) ; get cost statistics (for ([iter (in-list iteration-data)] [i (in-naturals)]) (define cnt (iteration-data-num-nodes iter)) (define cost (for/sum ([id (in-list root-ids)]) (egraph_get_cost egg-graph* id i))) (timeline-push! 'egraph i cnt cost (iteration-data-time iter))) egg-graph*)) ; root eclasses may have changed (define root-ids* (map (lambda (id) (egraph_find egg-graph* id)) root-ids)) ; return what we need (values root-ids* egg-graph*)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Public API ;; ;; Most calls to egg should be done through this interface. ;; - `make-egraph`: constructs an egraph and runs rules on it ;; - `egraph-equal?`: test if two expressions are equal ;; - `egraph-prove`: return a proof that two expressions are equal ;; - `egraph-best`: return a batch with the best versions of another batch ;; - `egraph-variations`: return a batch with all versions of another batch ;; Herbie's version of an egg runner. ;; Defines parameters for running rewrite rules with egg (struct egg-runner (batch reprs schedule ctx new-roots egg-graph) #:transparent ; for equality #:methods gen:custom-write ; for abbreviated printing [(define (write-proc alt port mode) (fprintf port "#"))]) ;; Constructs an egg runner. ;; ;; The schedule is a list of step symbols: ;; - `lift`: run lifting rules for 1 iteration with simple scheduler ;; - `rewrite`: run rewrite rules up to node limit with backoff scheduler ;; - `unsound`: run sound-removal rules for 1 iteration with simple scheduler ;; - `lower`: run lowering rules for 1 iteration with simple scheduler (define (make-egraph batch brfs reprs schedule ctx) (define (oops! fmt . args) (apply error 'verify-schedule! fmt args)) ; verify the schedule (for ([step (in-list schedule)]) (unless (memq step '(lift lower unsound rewrite)) (oops! "unknown schedule step `~a`" step))) (define-values (root-ids egg-graph) (egraph-run-schedule batch brfs schedule ctx)) ; make the runner (egg-runner batch reprs schedule ctx root-ids egg-graph)) (define (regraph-dump regraph root-ids reprs) (define dump-dir "dump-egg") (unless (directory-exists? dump-dir) (make-directory dump-dir)) (define name (for/first ([i (in-naturals)] #:unless (file-exists? (build-path dump-dir (format "~a.json" i)))) (build-path dump-dir (format "~a.json" i)))) (define nodes (regraph-nodes->json regraph)) (define canon (regraph-canon regraph)) (define roots (filter values (for/list ([id (in-list root-ids)] [type (in-list reprs)]) (hash-ref canon (cons id type) #f)))) (call-with-output-file name #:exists 'replace (lambda (p) (write-json (hash 'nodes nodes 'root_eclasses (map ~a roots) 'class_data (hash)) p)))) (define (egraph-equal? runner start end) (define ctx (egg-runner-ctx runner)) (define egg-graph (egg-runner-egg-graph runner)) (egraph-expr-equal? egg-graph start end ctx)) (define (egraph-prove runner start-brf end-brf) (define ctx (egg-runner-ctx runner)) (define egg-graph (egg-runner-egg-graph runner)) (define batch (egg-runner-batch runner)) (define exprs (batch-exprs batch)) (define start (exprs start-brf)) (define end (exprs end-brf)) (unless (egraph-expr-equal? egg-graph start end ctx) (error 'egraph-prove "cannot prove ~a is equal to ~a; not equal" start end)) (define proof (egraph-get-proof egg-graph start end ctx)) (when (null? proof) (error 'egraph-prove "proof extraction failed between`~a` and `~a`" start end)) proof) (define (egraph-best runner batch) (define ctx (egg-runner-ctx runner)) (define root-ids (egg-runner-new-roots runner)) (define egg-graph (egg-runner-egg-graph runner)) ; Return empty results if unsound (cond [(egraph_is_unsound_detected egg-graph) (map (const empty) root-ids)] [else (define regraph (make-regraph egg-graph ctx)) (define reprs (egg-runner-reprs runner)) (when (flag-set? 'dump 'egg) (regraph-dump regraph root-ids reprs)) (define extract-id ((typed-egg-batch-extractor batch) regraph)) ; (Listof (Listof batchref)) (for/list ([id (in-list root-ids)] [repr (in-list reprs)]) (regraph-extract-best regraph extract-id id repr))])) (define (egraph-variations runner batch) (define ctx (egg-runner-ctx runner)) (define root-ids (egg-runner-new-roots runner)) (define egg-graph (egg-runner-egg-graph runner)) ; Return empty results if unsound (cond [(egraph_is_unsound_detected egg-graph) (map (const empty) root-ids)] [else (define regraph (make-regraph egg-graph ctx)) (define reprs (egg-runner-reprs runner)) (when (flag-set? 'dump 'egg) (regraph-dump regraph root-ids reprs)) (define extract-id ((typed-egg-batch-extractor batch) regraph)) ; (Listof (Listof batchref)) (for/list ([id (in-list root-ids)] [repr (in-list reprs)]) (regraph-extract-variants regraph extract-id id repr))])) ================================================ FILE: src/core/egglog-herbie-tests.rkt ================================================ #lang racket (require rackunit "egglog-herbie.rkt" "egglog-subprocess.rkt" "../syntax/syntax.rkt") (define (test-e1->expr) (check-equal? (e1->expr '(Num (bigrat (from-string "3") (from-string "4")))) 3/4) (check-equal? (e1->expr '(Var "x")) 'x) (check-equal? (e1->expr '(If (Var "x") (Num (bigrat (from-string "1") (from-string "2"))) (Num (bigrat (from-string "0") (from-string "5"))))) '(if x 1/2 0)) (check-equal? (e1->expr '(Add (Num (bigrat (from-string "1") (from-string "1"))) (Num (bigrat (from-string "2") (from-string "1"))))) '(+ 1 2)) (check-equal? (e1->expr '(Mul (Num (bigrat (from-string "2") (from-string "1"))) (Num (bigrat (from-string "3") (from-string "1"))))) '(* 2 3)) (check-equal? (e1->expr '(Add (Mul (Var "a") (Div (Num (bigrat (from-string "3") (from-string "2"))) (Var "b"))) (Sub (Num (bigrat (from-string "5") (from-string "6"))) (Mul (Var "c") (Num (bigrat (from-string "-1") (from-string "4"))))))) '(+ (* a (/ 3/2 b)) (- 5/6 (* c -1/4)))) (check-equal? (e1->expr '(Div (Mul (Sub (Var "x") (Var "y")) (Add (Num (bigrat (from-string "7") (from-string "5"))) (Var "z"))) (Num (bigrat (from-string "2") (from-string "3"))))) '(/ (* (- x y) (+ 7/5 z)) 2/3)) (check-equal? (e1->expr '(Eq (Add (Mul (Var "p") (Num (bigrat (from-string "1") (from-string "2")))) (Div (Var "q") (Num (bigrat (from-string "3") (from-string "4"))))) (Num (bigrat (from-string "5") (from-string "6"))))) '(== (+ (* p 1/2) (/ q 3/4)) 5/6)) (check-equal? (e1->expr '(Gte (Div (Var "r") (Add (Var "s") (Var "t"))) (Sub (Num (bigrat (from-string "8") (from-string "9"))) (Mul (Num (bigrat (from-string "1") (from-string "3"))) (Var "u"))))) '(>= (/ r (+ s t)) (- 8/9 (* 1/3 u))))) (define (test-e2->expr) (check-equal? (e2->expr '(Numbinary64 (bigrat (from-string "5") (from-string "6")))) '#s(literal 5/6 binary64)) (check-equal? (e2->expr '(Varbinary64 "y")) 'y) (check-equal? (e2->expr '(Iff64Ty (Varbinary64 "y") (Numbinary64 (bigrat (from-string "1") (from-string "2"))) (Numbinary64 (bigrat (from-string "0") (from-string "1"))))) '(if.f64 y #s(literal 1/2 binary64) #s(literal 0 binary64))) (check-equal? (e2->expr '(Mulf64Ty (Numbinary64 (bigrat (from-string "2") (from-string "1"))) (Numbinary64 (bigrat (from-string "3") (from-string "1"))))) '(*.f64 #s(literal 2 binary64) #s(literal 3 binary64))) (check-equal? (e2->expr '(Approx (Add (Num (bigrat (from-string "1") (from-string "1"))) (Num (bigrat (from-string "2") (from-string "1")))) (Mulf64Ty (Numbinary64 (bigrat (from-string "3") (from-string "1"))) (Numbinary64 (bigrat (from-string "1") (from-string "1")))))) '#s(approx (+ 1 2) (*.f64 #s(literal 3 binary64) #s(literal 1 binary64)))) ;; Complex 1 (check-equal? (e2->expr '(Approx (Add (Sin (Add (Var "x") (Var "eps"))) (Mul (Num (bigrat (from-string "-1") (from-string "1"))) (Sin (Var "x")))) (Fmaf64Ty (Fmaf64Ty (Sinf64Ty (Varbinary64 "x")) (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "24")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "2")))) (Mulf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "6")))) (Cosf64Ty (Varbinary64 "x")))) (Mulf64Ty (Varbinary64 "eps") (Varbinary64 "eps")) (Mulf64Ty (Cosf64Ty (Varbinary64 "x")) (Varbinary64 "eps"))))) '#s(approx (+ (sin (+ x eps)) (* -1 (sin x))) (fma.f64 (fma.f64 (sin.f64 x) (fma.f64 (*.f64 eps #s(literal 1/24 binary64)) eps #s(literal -1/2 binary64)) (*.f64 (*.f64 eps #s(literal -1/6 binary64)) (cos.f64 x))) (*.f64 eps eps) (*.f64 (cos.f64 x) eps)))) ;; Complex 2 (check-equal? (e2->expr '(Mulf64Ty (Fmaf64Ty (Mulf64Ty (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "24")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "2")))) (Sinf64Ty (Varbinary64 "x"))) (Varbinary64 "eps") (Mulf64Ty (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "6")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "1")))) (Cosf64Ty (Varbinary64 "x")))) (Varbinary64 "eps"))) '(*.f64 (fma.f64 (*.f64 (fma.f64 (*.f64 eps #s(literal 1/24 binary64)) eps #s(literal -1/2 binary64)) (sin.f64 x)) eps (*.f64 (fma.f64 (*.f64 eps #s(literal -1/6 binary64)) eps #s(literal 1 binary64)) (cos.f64 x))) eps)) ;; Complex 3 (check-equal? (e2->expr '(Fmaf64Ty (Mulf64Ty (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "24")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "2")))) (Sinf64Ty (Varbinary64 "x"))) (Varbinary64 "eps") (Mulf64Ty (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "6")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "1")))) (Cosf64Ty (Varbinary64 "x"))))) '(fma.f64 (*.f64 (fma.f64 (*.f64 eps #s(literal 1/24 binary64)) eps #s(literal -1/2 binary64)) (sin.f64 x)) eps (*.f64 (fma.f64 (*.f64 eps #s(literal -1/6 binary64)) eps #s(literal 1 binary64)) (cos.f64 x)))) ;; Complex 4 (check-equal? (e2->expr '(Fmaf64Ty (Sinf64Ty (Varbinary64 "x")) (Fmaf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "1") (from-string "24")))) (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "2")))) (Mulf64Ty (Mulf64Ty (Varbinary64 "eps") (Numbinary64 (bigrat (from-string "-1") (from-string "6")))) (Cosf64Ty (Varbinary64 "x"))))) '(fma.f64 (sin.f64 x) (fma.f64 (*.f64 eps #s(literal 1/24 binary64)) eps #s(literal -1/2 binary64)) (*.f64 (*.f64 eps #s(literal -1/6 binary64)) (cos.f64 x)))) (check-equal? (e2->expr '(Approx (Add (Sin (Add (Var "x") (Var "eps")))) (Mulf64Ty (Numbinary64 (bigrat (from-string "-1") (from-string "1"))) (Cosf64Ty (Varbinary64 "x"))))) '#s(approx (+ (sin (+ x eps))) (*.f64 #s(literal -1 binary64) (cos.f64 x)))) (check-equal? (e2->expr '(Mulf32Ty (Numbinary32 (bigrat (from-string "3") (from-string "2"))) (Addf32Ty (Numbinary32 (bigrat (from-string "4") (from-string "5"))) (Varbinary32 "z")))) '(*.f32 #s(literal 3/2 binary32) (+.f32 #s(literal 4/5 binary32) z))) (check-equal? (e2->expr '(Iff32Ty (Varbinary32 "cond") (Numbinary32 (bigrat (from-string "7") (from-string "8"))) (Numbinary32 (bigrat (from-string "-2") (from-string "3"))))) '(if.f32 cond #s(literal 7/8 binary32) #s(literal -2/3 binary32)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Testing API ;; Test helpers for e1/e2 serialization tables. ;; - `populate-e->id-tables`: Used for testing e1-> expr and e2-> expr. ;; Populates the e1->id and e2->id tables (define (populate-e->id-tables) (begin (for ([op (in-list (all-operators))]) (hash-set! (e1->id) (serialize-op op) op)) (for-each (λ (pair) (hash-set! (e2->id) (car pair) (cdr pair))) '((Acosf32Ty . acos.f32) (Acosf64Ty . acos.f64) (Acoshf32Ty . acosh.f32) (Acoshf64Ty . acosh.f64) (Addf32Ty . +.f32) (Addf64Ty . +.f64) (AndboolTy . and.bool) (Asinf32Ty . asin.f32) (Asinf64Ty . asin.f64) (Asinhf32Ty . asinh.f32) (Asinhf64Ty . asinh.f64) (Atan2f32Ty . atan2.f32) (Atan2f64Ty . atan2.f64) (Atanf32Ty . atan.f32) (Atanf64Ty . atan.f64) (Atanhf32Ty . atanh.f32) (Atanhf64Ty . atanh.f64) (Cbrtf32Ty . cbrt.f32) (Cbrtf64Ty . cbrt.f64) (Ceilf32Ty . ceil.f32) (Ceilf64Ty . ceil.f64) (Copysignf32Ty . copysign.f32) (Copysignf64Ty . copysign.f64) (Cosf32Ty . cos.f32) (Cosf64Ty . cos.f64) (Coshf32Ty . cosh.f32) (Coshf64Ty . cosh.f64) (Divf32Ty . /.f32) (Divf64Ty . /.f64) (Ef32Ty . E.f32) (Ef64Ty . E.f64) (Eqf32Ty . ==.f32) (Eqf64Ty . ==.f64) (Erfcf32Ty . erfc.f32) (Erfcf64Ty . erfc.f64) (Erff32Ty . erf.f32) (Erff64Ty . erf.f64) (Exp2f32Ty . exp2.f32) (Exp2f64Ty . exp2.f64) (Expf32Ty . exp.f32) (Expf64Ty . exp.f64) (Expm1f32Ty . expm1.f32) (Expm1f64Ty . expm1.f64) (Fabsf32Ty . fabs.f32) (Fabsf64Ty . fabs.f64) (FalseboolTy . FALSE.bool) (Fdimf32Ty . fdim.f32) (Fdimf64Ty . fdim.f64) (Floorf32Ty . floor.f32) (Floorf64Ty . floor.f64) (Fmaf32Ty . fma.f32) (Fmaf64Ty . fma.f64) (Fmaxf32Ty . fmax.f32) (Fmaxf64Ty . fmax.f64) (Fminf32Ty . fmin.f32) (Fminf64Ty . fmin.f64) (Fmodf32Ty . fmod.f32) (Fmodf64Ty . fmod.f64) (Gtef32Ty . >=.f32) (Gtef64Ty . >=.f64) (Gtf32Ty . >.f32) (Gtf64Ty . >.f64) (Hypotf32Ty . hypot.f32) (Hypotf64Ty . hypot.f64) (Infinityf32Ty . INFINITY.f32) (Infinityf64Ty . INFINITY.f64) (Lgammaf32Ty . lgamma.f32) (Lgammaf64Ty . lgamma.f64) (Log10f32Ty . log10.f32) (Log10f64Ty . log10.f64) (Log1pf32Ty . log1p.f32) (Log1pf64Ty . log1p.f64) (Log2f32Ty . log2.f32) (Log2f64Ty . log2.f64) (Logbf32Ty . logb.f32) (Logbf64Ty . logb.f64) (Logf32Ty . log.f32) (Logf64Ty . log.f64) (Ltef32Ty . <=.f32) (Ltef64Ty . <=.f64) (Ltf32Ty . <.f32) (Ltf64Ty . <.f64) (Mulf32Ty . *.f32) (Mulf64Ty . *.f64) (Nanf32Ty . NAN.f32) (Nanf64Ty . NAN.f64) (Negf32Ty . neg.f32) (Negf64Ty . neg.f64) (Neqf32Ty . !=.f32) (Neqf64Ty . !=.f64) (NotboolTy . not.bool) (OrboolTy . or.bool) (Iff32Ty . if.f32) (Iff64Ty . if.f64) (Pif32Ty . PI.f32) (Pif64Ty . PI.f64) (Powf32Ty . pow.f32) (Powf64Ty . pow.f64) (Remainderf32Ty . remainder.f32) (Remainderf64Ty . remainder.f64) (Rintf32Ty . rint.f32) (Rintf64Ty . rint.f64) (Roundf32Ty . round.f32) (Roundf64Ty . round.f64) (Sinf32Ty . sin.f32) (Sinf64Ty . sin.f64) (Sinhf32Ty . sinh.f32) (Sinhf64Ty . sinh.f64) (Sqrtf32Ty . sqrt.f32) (Sqrtf64Ty . sqrt.f64) (Subf32Ty . -.f32) (Subf64Ty . -.f64) (Tanf32Ty . tan.f32) (Tanf64Ty . tan.f64) (Tanhf32Ty . tanh.f32) (Tanhf64Ty . tanh.f64) (Tgammaf32Ty . tgamma.f32) (Tgammaf64Ty . tgamma.f64) (TrueboolTy . TRUE.bool) (Truncf32Ty . trunc.f32) (Truncf64Ty . trunc.f64))))) ; run-tests (module+ test (require rackunit "egglog-herbie.rkt") (when (find-executable-path "egglog") (populate-e->id-tables) (test-e1->expr) (test-e2->expr))) ;; run-sample-egglog (module+ test (require rackunit "egglog-herbie.rkt" "../syntax/types.rkt" "../syntax/batch.rkt" "rules.rkt" "../config.rkt" "../syntax/platform.rkt" "../syntax/float.rkt" "../syntax/load-platform.rkt") (activate-platform! "c") (define-values (batch brfs) (progs->batch (list '(-.f64 (sin.f64 (+.f64 x eps)) (sin.f64 x)) '(sin.f64 (+.f64 x eps)) '(+.f64 x eps) 'x 'eps '(sin.f64 x)))) (define-values (batch2 brfs2) (progs->batch (list '(-.f64 (sin.f64 (+.f64 x #s(literal 1 binary64))) (sin.f64 x)) '(sin.f64 (+.f64 x #s(literal 1 binary64))) '(+.f64 x #s(literal 1 binary64)) 'x #s(literal 1 binary64) '(sin.f64 x) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (sin 1))) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (+ (sin 1) (* x (- (cos 1) 1))))) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (+ (sin 1) (* x (- (+ (cos 1) (* -1/2 (* x (sin 1)))) 1))))) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (+ (sin 1) (* x (- (+ (cos 1) (* x (+ (* -1/2 (sin 1)) (* x (+ 1/6 (* -1/6 (cos 1))))))) 1))))) #s(approx (sin (+ x 1)) #s(hole binary64 (+ (sin 1) (* x (cos 1))))) #s(approx (sin (+ x 1)) #s(hole binary64 (+ (sin 1) (* x (+ (cos 1) (* -1/2 (* x (sin 1)))))))) #s(approx (sin (+ x 1)) #s(hole binary64 (+ (sin 1) (* x (+ (cos 1) (* x (+ (* -1/2 (sin 1)) (* -1/6 (* x (cos 1)))))))))) #s(approx (+ x 1) #s(hole binary64 1)) #s(approx (+ x 1) #s(hole binary64 (+ 1 x))) #s(approx x #s(hole binary64 x)) #s(approx (sin x) #s(hole binary64 (* x (+ 1 (* -1/6 (pow x 2)))))) #s(approx (sin x) #s(hole binary64 (* x (+ 1 (* (pow x 2) (- (* 1/120 (pow x 2)) 1/6)))))) #s(approx (sin x) #s(hole binary64 (* x (+ 1 (* (pow x 2) (- (* (pow x 2) (+ 1/120 (* -1/5040 (pow x 2)))) 1/6)))))) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (- (sin (+ 1 x)) (sin x)))) #s(approx (sin (+ x 1)) #s(hole binary64 (sin (+ 1 x)))) #s(approx (+ x 1) #s(hole binary64 (* x (+ 1 (/ 1 x))))) #s(approx (sin x) #s(hole binary64 (sin x))) #s(approx (- (sin (+ x 1)) (sin x)) #s(hole binary64 (- (sin (- 1 (* -1 x))) (sin x)))) #s(approx (sin (+ x 1)) #s(hole binary64 (sin (- 1 (* -1 x)))))))) (define ctx (context '(x eps) (make-list 2 ))) (define reprs (make-list (length brfs) (context-repr ctx))) (define schedule '(lift rewrite lower)) (when (find-executable-path "egglog") (void (run-egglog (make-egglog-runner batch brfs reprs schedule ctx) batch #:extract 1000000)))) (module+ test (require rackunit) (when (find-executable-path "egglog") (define subproc (create-new-egglog-subprocess #f)) (define first-commands (list '(datatype Expr (Var String :cost 150) (Add Expr Expr :cost 200)) '(constructor const1 () Expr :unextractable) '(constructor const2 () Expr :unextractable) '(constructor const3 () Expr :unextractable) '(function unsound () bool :merge (or old new)) '(ruleset unsound-rule) '(set (unsound) false) '(rule ((= (Var c1) (Var c2)) (!= c1 c2)) ((set (unsound) true)) :ruleset unsound-rule) '(ruleset init) '(rule () ((let a1 (Var "x") ) (union (const1) a1) (let a2 (Var "y") ) (union (const2) a2) (let b1 (Add a1 a2) ) (union (const3) b1)) :ruleset init) '(run init 1))) ; Nothing to output (apply egglog-send subproc first-commands) ; Has extract 1 thing (define lines1 (egglog-extract subproc '(extract (const1) 1))) (check-equal? lines1 '((Var "x"))) ;; Print size (match-define (list node-values '() (list "false")) (egglog-send subproc '(print-size) '(run unsound-rule 1) '(extract (unsound)))) (define parsed-node-values (for/list ([entry (in-list (with-input-from-string (string-join node-values "\n") read))]) (match entry [(list relation count) (cons relation count)]))) (check-equal? (sort parsed-node-values symbolexpr e1->expr egglog-var? serialize-op e1->id e2->id) (define op-string-names (hash '+ 'Add '- 'Sub '* 'Mul '/ 'Div '== 'Eq '!= 'Neq '> 'Gt '< 'Lt '>= 'Gte '<= 'Lte)) (define/reset id->e1 (make-hasheq)) (define/reset e1->id (make-hasheq)) (define/reset id->e2 (make-hasheq)) (define/reset e2->id (make-hasheq)) ;; [Copied from egg-herbie.rkt] Returns all representatations (and their types) in the current platform. (define (all-repr-names [pform (*active-platform*)]) (map representation-name (platform-reprs pform))) (define (real->bigrat val) `(bigrat (from-string ,(~s (numerator val))) (from-string ,(~s (denominator val))))) ; Types handled ; - rationals ; - string ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Public API ;; Most calls to egglog should be done through this interface. ;; - `make-egglog-runner`: creates a struct that describes a _reproducible_ egglog instance ;; - `run-egglog`: takes an egglog runner and performs an extraction (exprs or proof) ;; Herbie's version of an egglog runner. ;; Defines parameters for running rewrite rules with egglog (struct egglog-runner (batch brfs reprs schedule ctx) #:transparent ; for equality #:methods gen:custom-write ; for abbreviated printing [(define (write-proc alt port mode) (fprintf port "#"))]) ;; Constructs an egglog runner - structurally serves the same purpose as egg-runner ;; ;; The schedule is a list of step symbols: ;; - `lift`: run lifting rules for 1 iteration with simple scheduler ;; - `rewrite`: run rewrite rules up to node limit with backoff scheduler ;; - `unsound`: run sound-removal rules for 1 iteration with simple scheduler ;; - `lower`: run lowering rules for 1 iteration with simple scheduler (define (make-egglog-runner batch brfs reprs schedule ctx) (define (oops! fmt . args) (apply error 'verify-schedule! fmt args)) ; verify the schedule (for ([step (in-list schedule)]) (unless (memq step '(lift lower unsound rewrite)) (oops! "unknown schedule step `~a`" step))) ; make the runner (egglog-runner batch brfs reprs schedule ctx)) ;; Runs egglog using an egglog runner by extracting multiple variants (define (run-egglog runner output-batch [label #f] #:extract extract) ; multi expression extraction (define insert-batch (egglog-runner-batch runner)) (define insert-brfs (egglog-runner-brfs runner)) (define schedule (egglog-runner-schedule runner)) (define pform (*active-platform*)) ;;;; SUBPROCESS START ;;;; (define subproc (create-new-egglog-subprocess label)) ;; 1. Add the prelude - send directly to egglog. (prelude subproc #:mixed-egraph? #t) ;; 2. Inserting expressions into the egglog program and getting a Listof (exprs . extract bindings) ;; Overview of the new extraction method: ;; ;; The idea is to wrap the top-level `let` bindings inside a rule, and then ;; execute that rule to perform the computation and store results using constructors. ;; ;; In the original design, we had a sequence of `let` bindings followed by a schedule run and ;; then we perform an extraction of the required bindings. ;; ;; The new design introduces unextractable constructors to hold each intermediate result. ;; These constructors are used in combination with a rule that performs all the bindings ;; and assigns them via `set` ;; ;; (constructor const1 () Expr :unextractable) ;; (constructor const2 () Expr :unextractable) ;; (constructor const3 () Expr :unextractable) ;; ... ;; ;; (ruleset init) ;; ;; (rule () ( ;; (let a1 ...) ;; (union (const1) a1) ;; ;; (let a2 ...) ;; (union (const2) a2) ;; ;; (let b1 ...) ;; (union (const3) b1) ;; ) ;; :ruleset init) ;; ;; (run init 1) ;; (extract (const1)) ;; (extract (const2)) ;; (extract (const3)) ;; ;; ;; The idea behind this updated design is to prevent egglog from constantly rebuilding which ;; it does after every top level command. Hence, we wrap the top level bindings into the actions ;; of a rule and make them accessible through their unique constructor. Therefore, we must ;; keep track of the mapping between each binding and its corresponding constructor. (define-values (all-bindings extract-bindings) (egglog-add-exprs insert-batch insert-brfs (egglog-runner-ctx runner) subproc)) (egglog-send subproc `(ruleset run-extract-commands) `(rule () (,@all-bindings) :ruleset run-extract-commands) `(run-schedule (repeat 1 run-extract-commands))) ;; 4. Running the schedule : having code inside to emulate egraph-run-rules (for ([step (in-list schedule)]) (apply egglog-send subproc (egglog-step-commands step pform)) (match step ['lift (egglog-send subproc '(run-schedule (saturate lift)))] ['lower (egglog-send subproc '(run-schedule (saturate lower)))] ['unsound (egglog-send subproc '(run-schedule (saturate unsound)))] ;; Run the rewrite ruleset interleaved with const-fold until the best iteration ['rewrite (egglog-unsound-detected-subprocess step subproc)])) ;; 5. Extract using constructor names returned by egglog-add-exprs. (define stdout-content (egglog-multi-extract subproc `(multi-extract ,extract ,@(for/list ([constructor-name extract-bindings]) `(,constructor-name))))) ;; Close everything subprocess related (egglog-subprocess-close subproc) ;; (Listof (Listof exprs)) (define herbie-exprss (for/list ([next-expr (in-list stdout-content)]) (map e2->expr next-expr))) (for/list ([variants (in-list herbie-exprss)]) (for/list ([v (in-list variants)]) (batch-add! output-batch v)))) ;; Egglog requires integer costs, but Herbie uses floating-point costs. ;; Scale by 1000 to convert Herbie's float costs to Egglog's integer costs. (define (normalize-cost c) (exact-round (* c 1000))) (define (prelude subproc #:mixed-egraph? [mixed-egraph? #t]) (define pform (*active-platform*)) (egglog-send subproc `(datatype M ,@(platform-spec-nodes))) (egglog-send subproc `(datatype MTy ,@(num-typed-nodes pform) ,@(var-typed-nodes pform) (Approx M MTy) ,@(platform-impl-nodes pform)) `(constructor do-lower (M String) MTy :unextractable) `(constructor do-lift (MTy) M :unextractable) `(ruleset lower) `(ruleset lift) `(ruleset unsound) `(function bad-merge? () bool :merge (or old new)) `(ruleset bad-merge-rule) `(set (bad-merge?) false) `(rule ((= (Num c1) (Num c2)) (!= c1 c2)) ((set (bad-merge?) true)) :ruleset bad-merge-rule)) (void)) (define (egglog-step-commands step pform) (match step ['lift (append (list (approx-lifting-rule)) (impl-lifting-rules pform) (num-lifting-rules))] ['lower (append (impl-lowering-rules pform) (num-lowering-rules))] ['unsound (egglog-rewrite-rules (*sound-removal-rules*) 'unsound)] ['rewrite (append (list `(ruleset rewrite)) (const-fold-rules) (egglog-rewrite-rules (*rules*) 'rewrite))])) (define (const-fold-rules) `((ruleset const-fold) (let $0 ,(real->bigrat 0) ) (let $1 ,(real->bigrat 1) ) (rewrite (Add (Num x) (Num y)) (Num (+ x y)) :ruleset const-fold) (rewrite (Sub (Num x) (Num y)) (Num (- x y)) :ruleset const-fold) (rewrite (Mul (Num x) (Num y)) (Num (* x y)) :ruleset const-fold) ; TODO : Non-total operator (rule ((= e (Div (Num x) (Num y))) (!= $0 y)) ((union e (Num (/ x y)))) :ruleset const-fold) (rewrite (Neg (Num x)) (Num (neg x)) :ruleset const-fold) ;; Power rules -> only case missing is 0^0 making it non-total ;; 0^y where y > 0 (rule ((= e (Pow (Num x) (Num y))) (= $0 x) (> y $0)) ((union e (Num $0))) :ruleset const-fold) ;; x^0 where x != 0 (rule ((= e (Pow (Num x) (Num y))) (= $0 y) (!= $0 x)) ((union e (Num $1))) :ruleset const-fold) ;; x^y when y is a whole number and y > 0 and x != 0 (rule ((= e (Pow (Num x) (Num y))) (> y $0) (!= $0 x) (= y (round y))) ((union e (Num (pow x y)))) :ruleset const-fold) ;; New rule according to Rust : x^y where y is not a whole number (rule ((= e (Pow (Num x) (Num y))) (> y $0) (!= $0 x) (!= y (round y))) ((union e (Num (pow x (round y))))) :ruleset const-fold) ;; Sqrt rules -> Non-total but egglog implementation handles it (rule ((= e (Sqrt (Num n))) (sqrt n)) ((union e (Num (sqrt n)))) :ruleset const-fold) (rewrite (Log (Num $1)) (Num $0) :ruleset const-fold) (rewrite (Cbrt (Num $1)) (Num $1) :ruleset const-fold) (rewrite (Fabs (Num x)) (Num (abs x)) :ruleset const-fold) (rewrite (Floor (Num x)) (Num (floor x)) :ruleset const-fold) (rewrite (Ceil (Num x)) (Num (ceil x)) :ruleset const-fold) (rewrite (Round (Num x)) (Num (round x)) :ruleset const-fold))) (define (platform-spec-nodes) (for ([op '(sound-/ sound-log sound-pow)]) (hash-set! (id->e1) op (serialize-op op)) (hash-set! (e1->id) (serialize-op op) op)) (hash-set! (id->e1) 'array 'Array) (hash-set! (e1->id) 'Array 'array) (hash-set! (e1->id) 'Array3 'array) (list* '(Num BigRat :cost 4294967295) '(Var String :cost 4294967295) '(Sound-/ M M M :cost 4294967295) '(Sound-Log M M :cost 4294967295) '(Sound-Pow M M M :cost 4294967295) '(Array M M :cost 4294967295) '(Array3 M M M :cost 4294967295) (for/list ([op (in-list (all-operators))] #:unless (eq? op 'array)) (define arity (length (operator-info op 'itype))) (hash-set! (id->e1) op (serialize-op op)) (hash-set! (e1->id) (serialize-op op) op) `(,(serialize-op op) ,@(make-list arity 'M) :cost 4294967295)))) (define (platform-impl-nodes pform) (for/list ([impl (in-list (platform-impls pform))]) (define arity (length (impl-info impl 'itype))) (define typed-name (string->symbol (format "~aTy" (serialize-impl impl)))) (hash-set! (id->e2) impl typed-name) (hash-set! (e2->id) typed-name impl) (define cost (normalize-cost (impl-info impl 'cost))) `(,typed-name ,@(make-list arity 'MTy) :cost ,cost))) (define (typed-num-id repr-name) (string->symbol (format "Num~a" repr-name))) (define (typed-var-id repr-name) (string->symbol (format "Var~a" repr-name))) (define (num-typed-nodes pform) (for/list ([repr (in-list (all-repr-names))] #:when (not (eq? repr 'bool))) (define cost (normalize-cost (platform-repr-cost pform (get-representation repr)))) `(,(typed-num-id repr) BigRat :cost ,cost))) (define (var-typed-nodes pform) (for/list ([repr (in-list (all-repr-names))]) (define cost (normalize-cost (platform-repr-cost pform (get-representation repr)))) `(,(typed-var-id repr) String :cost ,cost))) (define (num-lowering-rules) (for/list ([repr (in-list (all-repr-names))] #:when (not (eq? repr 'bool))) `(rule ((= e (Num n))) ((union (do-lower e ,(symbol->string repr)) (,(typed-num-id repr) n))) :ruleset lower))) (define (num-lifting-rules) (for/list ([repr (in-list (all-repr-names))] #:when (not (eq? repr 'bool))) `(rule ((= e (,(typed-num-id repr) n))) ((union (do-lift e) (Num n))) :ruleset lift))) (define (approx-lifting-rule) `(rule ((= e (Approx spec impl))) ((union (do-lift e) spec)) :ruleset lift)) (define (impl-lowering-rules pform) (for/list ([impl (in-list (platform-impls pform))]) (define spec-expr (impl-info impl 'spec)) `(rule ((= e ,(expr->egglog-spec-serialized spec-expr "")) ,@(for/list ([v (in-list (impl-info impl 'vars))] [vt (in-list (impl-info impl 'itype))]) `(= ,(string->symbol (string-append "t" (symbol->string v))) (do-lower ,v ,(symbol->string (representation-name vt)))))) ((union (do-lower e ,(symbol->string (representation-name (impl-info impl 'otype)))) (,(string->symbol (string-append (symbol->string (serialize-impl impl)) "Ty")) ,@(for/list ([v (in-list (impl-info impl 'vars))]) (string->symbol (string-append "t" (symbol->string v))))))) :ruleset lower))) (define (impl-lifting-rules pform) (for/list ([impl (in-list (platform-impls pform))]) (define spec-expr (impl-info impl 'spec)) `(rule ((= e (,(string->symbol (string-append (symbol->string (serialize-impl impl)) "Ty")) ,@(impl-info impl 'vars))) ,@(for/list ([v (in-list (impl-info impl 'vars))] [vt (in-list (impl-info impl 'itype))]) `(= ,(string->symbol (string-append "s" (symbol->string v))) (do-lift ,v)))) ((union (do-lift e) ,(expr->egglog-spec-serialized spec-expr "s"))) :ruleset lift))) (define (serialize-spec-op op arity) (match* (op arity) [('array 2) 'Array] [('array 3) 'Array3] [(_ _) (hash-ref (id->e1) op)])) (define (expr->egglog-spec-serialized expr s) (let loop ([expr expr]) (match expr [(? number?) `(Num ,(real->bigrat expr))] [(? symbol?) (string->symbol (string-append s (symbol->string expr)))] [(list op args ...) `(,(if (hash-has-key? (id->e1) op) (serialize-spec-op op (length args)) (hash-ref (id->e2) op)) ,@(map loop args))]))) (define (serialize-op op) (if (hash-has-key? op-string-names op) (hash-ref op-string-names op) (string->symbol (string-titlecase (symbol->string op))))) (define (serialize-impl impl) (define impl-split (string-split (symbol->string impl) ".")) (define op (string->symbol (car impl-split))) (define type (if (null? (cdr impl-split)) "" (string-join (cdr impl-split) ""))) (string->symbol (string-append (symbol->string (serialize-op op)) type))) (define (expr->e1-pattern expr) (let loop ([expr expr]) (match expr [(? number?) `(Num ,(real->bigrat expr))] [(? symbol?) expr] [(list op args ...) `(,(serialize-spec-op op (length args)) ,@(map loop args))]))) (define (egglog-rewrite-rules rules tag) (for/list ([rule (in-list rules)] #:when (not (symbol? (rule-input rule)))) `(rewrite ,(expr->e1-pattern (rule-input rule)) ,(expr->e1-pattern (rule-output rule)) :ruleset ,tag))) (define (egglog-add-exprs batch brfs ctx subproc) (define mappings (build-vector (batch-length batch) values)) (define bindings (make-hash)) (define vars (make-hash)) (define (remap x spec?) (cond [(hash-has-key? vars x) (if spec? (string->symbol (format "?s~a" (hash-ref vars x))) (string->symbol (format "?t~a" (hash-ref vars x))))] [else (vector-ref mappings x)])) ; node -> egglog node binding ; inserts an expression into the e-graph, returning binding variable. (define (insert-node! node n root?) (define binding (if root? (string->symbol (format "?r~a" n)) (string->symbol (format "?b~a" n)))) (hash-set! bindings binding node) binding) (define root-bindings '()) ; Inserting nodes bottom-up (define root-mask (make-vector (batch-length batch) #f)) ;; Batchref -> Boolean (define spec? (batch-recurse batch (lambda (brf recurse) (define node (deref brf)) (match node [(? literal?) #f] ;; If literal, not a spec [(? number?) #t] ;; If number, it's a spec [(? symbol?) #f] ;; If symbol, assume not a spec could be either (find way to distinguish) : PREPROCESS [(hole _ _) #f] ;; If hole, not a spec [(approx _ _) #f] ;; If approx, not a spec [`(if ,cond ,ift ,iff) (recurse cond)] ;; If the condition or any branch is a spec, then this is a spec [(list appl args ...) (if (hash-has-key? (id->e1) appl) #t ;; appl with op -> Is a spec #f)])))) ;; appl impl -> Not a spec (for ([brf (in-list brfs)]) (vector-set! root-mask (batchref-idx brf) #t)) (for ([node (in-batch batch)] [root? (in-vector root-mask)] [n (in-naturals)]) (define node* (match node [(literal v repr) `(,(typed-num-id repr) ,(real->bigrat v))] [(? number?) `(Num ,(real->bigrat node))] [(? symbol?) #f] [(approx spec impl) `(Approx ,(remap spec #t) ,(remap impl #f))] [(list impl args ...) `(,(hash-ref (if (spec? (batchref batch n)) (id->e1) (id->e2)) impl) ,@(for/list ([arg (in-list args)]) (remap arg (spec? (batchref batch n)))))] [(hole ty spec) `(do-lower ,(remap spec #t) ,(symbol->string ty))])) (if node* (vector-set! mappings n (insert-node! node* n root?)) (hash-set! vars n node)) (when root? (set! root-bindings (cons (vector-ref mappings n) root-bindings)))) ; Var-lowering-rules (for ([var (in-list (context-vars ctx))] [repr (in-list (context-var-reprs ctx))]) (egglog-send subproc `(rule ((= e (Var ,(symbol->string var)))) ((union (do-lower e ,(symbol->string (representation-name repr))) (,(typed-var-id (representation-name repr)) ,(symbol->string var)))) :ruleset lower))) ; Var-lifting-rules (for ([var (in-list (context-vars ctx))] [repr (in-list (context-var-reprs ctx))]) (egglog-send subproc `(rule ((= e (,(typed-var-id (representation-name repr)) ,(symbol->string var)))) ((union (do-lift e) (Var ,(symbol->string var)))) :ruleset lift))) (define all-bindings '()) (define binding->constructor (make-hash)) ; map from binding name to constructor name (define constructor-num 1) ; ; Var-spec-bindings (for ([var (in-list (context-vars ctx))]) ; Get the binding names for the program (define binding-name (string->symbol (format "?s~a" var))) (define constructor-name (string->symbol (format "const~a" constructor-num))) (hash-set! binding->constructor binding-name constructor-name) ; Define the actual binding (define curr-var-spec-binding `(let ,binding-name (Var ,(symbol->string var)))) ; Send the constructor definition (egglog-send subproc `(constructor ,constructor-name () M :unextractable)) ; Add the binding and constructor union to all-bindings for the future rule (set! all-bindings (cons curr-var-spec-binding all-bindings)) (set! all-bindings (cons `(union (,constructor-name) ,binding-name) all-bindings)) (set! constructor-num (add1 constructor-num))) ; Var-typed-bindings (for ([var (in-list (context-vars ctx))] [repr (in-list (context-var-reprs ctx))]) ; Get the binding names for the program (define binding-name (string->symbol (format "?t~a" var))) (define constructor-name (string->symbol (format "const~a" constructor-num))) (hash-set! binding->constructor binding-name constructor-name) ; Define the actual binding (define curr-var-typed-binding `(let ,binding-name (,(typed-var-id (representation-name repr)) ,(symbol->string var)))) ; Send the constructor definition (egglog-send subproc `(constructor ,constructor-name () MTy :unextractable)) ; Add the binding and constructor union to all-bindings for the future rule (set! all-bindings (cons curr-var-typed-binding all-bindings)) (set! all-bindings (cons `(union (,constructor-name) ,binding-name) all-bindings)) (set! constructor-num (add1 constructor-num))) ; Binding Exprs (for ([root? (in-vector root-mask)] [n (in-naturals)] #:when (not (hash-has-key? vars n))) (define binding-name (if root? (string->symbol (format "?r~a" n)) (string->symbol (format "?b~a" n)))) (define constructor-name (string->symbol (format "const~a" constructor-num))) (hash-set! binding->constructor binding-name constructor-name) (define actual-binding (hash-ref bindings binding-name)) (define curr-datatype (match actual-binding [(cons 'do-lower _) 'MTy] [(cons 'do-lift _) 'M] ;; TODO : fix this way of getting spec or impl [_ (if root? 'MTy 'M)])) (define curr-binding-exprs `(let ,binding-name ,actual-binding)) (egglog-send subproc `(constructor ,constructor-name () ,curr-datatype :unextractable)) (set! all-bindings (cons curr-binding-exprs all-bindings)) (set! all-bindings (cons `(union (,constructor-name) ,binding-name) all-bindings)) (set! constructor-num (add1 constructor-num))) (define curr-bindings (for/list ([brf brfs]) (define root (batchref-idx brf)) (define curr-binding-name (if (hash-has-key? vars root) (if (spec? brf) (string->symbol (format "?s~a" (hash-ref vars root))) (string->symbol (format "?t~a" (hash-ref vars root)))) (string->symbol (format "?r~a" root)))) (hash-ref binding->constructor curr-binding-name))) (values (reverse all-bindings) curr-bindings)) (define (egglog-unsound-detected-subprocess tag subproc) (define node-limit (*node-limit*)) (define iter-limit (*default-egglog-iter-limit*)) ;; Use egglog's :until guard with get-size! to stop when node limit is reached. ;; After each iteration, we check for unsound merges via bad-merge-rule. ;; The schedule runs until: ;; 1. Node limit is reached (get-size! >= node-limit) ;; 2. Saturation (no more progress) ;; 3. Iter limit is reached ;; 4. Unsoundness is detected (bad-merge? becomes true) (egglog-send subproc `(run-schedule (repeat ,iter-limit (seq (run ,tag :until (<= ,node-limit (get-size!))) (run const-fold :until (<= ,node-limit (get-size!))) (run bad-merge-rule :until (bad-merge?)))))) (void)) (define (egglog-num? id) (string-prefix? (symbol->string id) "Num")) (define (egglog-num-repr id) (string->symbol (substring (symbol->string id) 3))) (define (egglog-var? id) (string-prefix? (symbol->string id) "Var")) (define (e1->expr expr) (match expr [`(Num (bigrat (from-string ,n) (from-string ,d))) (/ (string->number n) (string->number d))] [`(Var ,v) (string->symbol v)] [`(,op ,args ...) `(,(hash-ref (e1->id) op) ,@(map e1->expr args))])) (define (e2->expr expr) (match expr [`(,(? egglog-num? num) (bigrat (from-string ,n) (from-string ,d))) (literal (/ (string->number n) (string->number d)) (egglog-num-repr num))] [`(,(? egglog-var? var) ,v) (string->symbol v)] ; Approx stores a spec expression in E1/M and an implementation in E2/MTy. [`(Approx ,spec ,impl) (approx (e1->expr spec) (e2->expr impl))] [`(,impl ,args ...) `(,(hash-ref (e2->id) impl) ,@(map e2->expr args))])) ================================================ FILE: src/core/egglog-subprocess.rkt ================================================ #lang racket (require "../config.rkt") (provide (struct-out egglog-subprocess) create-new-egglog-subprocess egglog-send egglog-extract egglog-multi-extract egglog-subprocess-close) ;; Struct to hold egglog subprocess handles (struct egglog-subprocess (process output input error dump-file) #:transparent) ;; Close all ports and wait for/kill the subprocess (define (egglog-subprocess-close subproc) (close-output-port (egglog-subprocess-input subproc)) (close-input-port (egglog-subprocess-output subproc)) (subprocess-wait (egglog-subprocess-process subproc)) (unless (eq? (subprocess-status (egglog-subprocess-process subproc)) 'done) (subprocess-kill (egglog-subprocess-process subproc) #f))) ;; High-level function that writes the program to a file, runs it then returns output ;; ;; If the flag is set to dump the egglog file, creates a new dump file in dump-egglog/ directory (define (create-new-egglog-subprocess [label #f]) (define egglog-path (or (find-executable-path "egglog-experimental") (find-executable-path "egglog") (error "egglog-experimental executable not found in PATH"))) (define-values (egglog-process egglog-output egglog-in err) (subprocess #f #f (current-error-port) egglog-path "--mode=interactive")) ;; Create dump file if flag is set (define dump-file (cond [(flag-set? 'dump 'egglog) (define dump-dir "dump-egglog") (unless (directory-exists? dump-dir) (make-directory dump-dir)) (define name (for/first ([i (in-naturals)] #:unless (file-exists? (build-path dump-dir (format "~a~a.egg" (if label label "") i)))) (build-path dump-dir (format "~a~a.egg" (if label label "") i)))) (open-output-file name #:exists 'replace)] [else #f])) (egglog-subprocess egglog-process egglog-output egglog-in err dump-file)) (define (egglog-send subproc . commands) (match-define (egglog-subprocess egglog-process egglog-output egglog-in err dump-file) subproc) (when dump-file (for ([expr commands]) (pretty-print expr dump-file 1)) (flush-output dump-file)) (for/list ([command (in-list commands)]) (writeln command egglog-in) (flush-output egglog-in) (let loop ([out '()]) (define next (read-line egglog-output)) (if (equal? next "(done)") (reverse out) (loop (cons next out)))))) ;; Send extract commands and read results (define (egglog-extract subproc extract-command) (match-define (list "(" results ... ")") (first (egglog-send subproc extract-command))) (for/list ([result (in-list results)]) (read (open-input-string result)))) (define (egglog-multi-extract subproc extract-command) (define raw-lines (first (egglog-send subproc extract-command))) (define combined (string-join raw-lines " ")) (define parsed (read (open-input-string combined))) (for/list ([result-list (in-list parsed)]) (for/list ([result (in-list result-list)]) result))) ================================================ FILE: src/core/explain.rkt ================================================ #lang racket (require racket/set math/bigfloat math/flonum racket/hash) (require "../utils/common.rkt" "../syntax/float.rkt" "../syntax/types.rkt" "../syntax/syntax.rkt" "../syntax/platform.rkt" "../syntax/batch.rkt" "localize.rkt" "points.rkt" "programs.rkt" "sampling.rkt") (provide explain) (define *top-3* (make-parameter #f)) (define (take-top-n lst) (if (*top-3*) (take-n 3 lst) lst)) (define (take-n n lst) (match lst ['() '()] [(cons x xs) (if (= n 0) '() (cons x (take-n (- n 1) xs)))])) (define (constant? expr) (cond [(list? expr) (andmap constant? (rest expr))] [(symbol? expr) #f] [else #t])) (define (actual-errors expr ctx pcontext) (match-define (cons subexprs pt-errorss) (apply map list (hash->list (first (compute-local-errors (list (all-subexpressions expr)) ctx pcontext))))) (define pt-worst-subexpr (append* (reap [sow] (for ([pt-errors (in-list pt-errorss)] [(pt _) (in-pcontext pcontext)]) (define sub-error (map cons subexprs pt-errors)) (define filtered-sub-error (filter (lambda (p) (> (cdr p) 16)) sub-error)) (define mapped-sub-error (map (lambda (p) (cons (car p) pt)) filtered-sub-error)) (unless (empty? mapped-sub-error) (sow mapped-sub-error)))))) (for/hash ([group (in-list (group-by car pt-worst-subexpr))]) (define key (caar group)) (values key (map cdr group)))) (define (same-sign? a b) (or (and (bfpositive? a) (bfpositive? b)) (and (bfnegative? a) (bfnegative? b)))) (define all-explanations (list 'uflow-rescue 'u/u 'u/n 'o/o 'n/o 'o*u 'u*o 'n*u 'cancellation)) (define cond-thres (bf 100)) (define maybe-cond-thres (bf 32)) (define (compile-expr expr ctx) (define subexprs (all-subexpressions expr #:reverse? #t)) (define spec-list (map prog->spec subexprs)) (define ctxs (for/list ([subexpr (in-list subexprs)]) (struct-copy context ctx [repr (repr-of subexpr ctx)]))) (define repr-hash (make-immutable-hash (map (lambda (e ctx) (cons e (context-repr ctx))) subexprs ctxs))) (define-values (batch brfs) (progs->batch spec-list)) (define subexprs-fn (parameterize ([*max-mpfr-prec* 128]) (eval-progs-real batch brfs ctxs))) (values subexprs repr-hash subexprs-fn)) (define (predict-errors ctx pctx subexprs-list repr-hash subexprs-fn) (define error-count-hash (make-hash (map (lambda (x) (cons x '())) subexprs-list))) (define uflow-hash (make-hash)) (define oflow-hash (make-hash)) (define expls->points (make-hash)) (define maybe-expls->points (make-hash)) (for ([(pt _) (in-pcontext pctx)]) (define (silence expr) (define subexprs (all-subexpressions expr #:reverse? #t)) (for* ([subexpr (in-list subexprs)] #:when (list? subexpr) [expl (in-list all-explanations)]) (define key (cons subexpr expl)) (when (hash-has-key? expls->points key) (hash-update! expls->points key (lambda (x) (set-remove x pt)))) (when (hash-has-key? maybe-expls->points key) (hash-update! maybe-expls->points key (lambda (x) (set-remove x pt)))))) (define (mark-erroneous! expr expl) (hash-update! error-count-hash expr (lambda (x) (set-add x pt))) (hash-update! expls->points (cons expr expl) (lambda (x) (set-add x pt)) '())) (define (mark-maybe! expr [expl 'sensitivity]) (hash-update! maybe-expls->points (cons expr expl) (lambda (x) (set-add x pt)) '())) (define exacts (subexprs-fn pt)) (define exacts-hash (make-immutable-hash (map cons subexprs-list exacts))) (define (exacts-ref subexpr) (define exacts-val (hash-ref exacts-hash subexpr)) ((representation-repr->bf (hash-ref repr-hash subexpr)) exacts-val)) (for/list ([subexpr (in-list subexprs-list)]) (define subexpr-val (exacts-ref subexpr)) (define (update-flow-hash flow-hash pred? . children) (define child-set (foldl (lambda (a b) (hash-union a b #:combine +)) (make-immutable-hash) (map (lambda (a) (hash-ref flow-hash a (make-immutable-hash))) children))) (define parent-set (hash-ref flow-hash subexpr (make-immutable-hash))) (define parent+child-set (hash-union parent-set child-set #:combine (lambda (_ v) v))) (define new-parent-set (if (and (bigfloat? subexpr-val) (pred? subexpr-val)) (hash-update parent+child-set subexpr add1 0) parent+child-set)) (hash-set! flow-hash subexpr new-parent-set)) (match subexpr [(list _ x-ex y-ex z-ex) (update-flow-hash oflow-hash bfinfinite? x-ex y-ex z-ex) (update-flow-hash uflow-hash bfzero? x-ex y-ex z-ex)] [(list _ x-ex y-ex) (update-flow-hash oflow-hash bfinfinite? x-ex y-ex) (update-flow-hash uflow-hash bfzero? x-ex y-ex)] [(list _ x-ex) (update-flow-hash oflow-hash bfinfinite? x-ex) (update-flow-hash uflow-hash bfzero? x-ex)] [_ #f]) (match subexpr [(list (or '+.f64 '+.f32) x-ex y-ex) #:when (or (list? x-ex) (list? y-ex)) (define x (exacts-ref x-ex)) (define y (exacts-ref y-ex)) (define x+y (bigfloat->flonum (bf+ x y))) (define cond-x (bfabs (bf/ x subexpr-val))) (define cond-y (bfabs (bf/ y subexpr-val))) (define x.eps (+ 127 (bigfloat-exponent x))) (define y.eps (+ 127 (bigfloat-exponent y))) (cond [(> (- x.eps y.eps) 100) (silence y-ex)] [(> (- y.eps x.eps) 100) (silence x-ex)] [else (void)]) (cond ; Condition number hallucination ; Both R(x + y) and R(x) + R(y) underflow ; This causes the condition number to jump up, ; with no real error [(and (= x+y 0.0) (bfzero? subexpr-val)) #f] ; nan rescue: ; R(+-inf) + R(-+inf) = nan, but should actually ; be inf [(and (bfinfinite? x) (bfinfinite? y) (not (same-sign? x y)) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'nan-rescue)] ; inf rescue: ; R(inf) + y = non inf value (inf rescue) [(and (bfinfinite? x) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'oflow-left)] [(and (bfinfinite? y) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'oflow-right)] ; High condition number: ; CN(+, x, y) = |x / x + y| [(or (bf> cond-x cond-thres) (bf> cond-y cond-thres)) (mark-erroneous! subexpr 'cancellation)] [(or (bf> cond-x maybe-cond-thres) (bf> cond-y maybe-cond-thres)) (mark-maybe! subexpr 'cancellation)] [else #f])] [(list (or '-.f64 '-.f32) x-ex y-ex) #:when (or (list? x-ex) (list? y-ex)) (define x (exacts-ref x-ex)) (define y (exacts-ref y-ex)) (define x-y (bigfloat->flonum (bf- x y))) (define cond-x (bfabs (bf/ x subexpr-val))) (define cond-y (bfabs (bf/ y subexpr-val))) (define x.eps (+ 127 (bigfloat-exponent x))) (define y.eps (+ 127 (bigfloat-exponent y))) (cond [(> (- x.eps y.eps) 100) (silence y-ex)] [(> (- y.eps x.eps) 100) (silence x-ex)] [else (void)]) (cond ; Condition number hallucination: ; When x - y correctly underflows, CN is high ; even though the answer is correct [(and (= x-y 0.0) (bfzero? subexpr-val)) #f] ; nan rescue: ; inf - inf = nan but should actually get an inf [(and (bfinfinite? x) (bfinfinite? y) (same-sign? x y) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'nan-rescue)] ; inf rescue ; If x or y overflow and the other arg rescues ; it [(and (bfinfinite? x) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'oflow-left)] [(and (bfinfinite? y) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'oflow-right)] ; High condition number: ; CN(+, x, y) = |x / x - y| [(or (bf> cond-x cond-thres) (bf> cond-y cond-thres)) (mark-erroneous! subexpr 'cancellation)] [(or (bf> cond-x maybe-cond-thres) (bf> cond-y maybe-cond-thres)) (mark-maybe! subexpr 'cancellation)] [else #f])] [(list (or 'sin.f64 'sin.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define cot-x (bfabs (bfcot x))) (define cond-no (bf* (bfabs x) cot-x)) (cond [(and (bfinfinite? x) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'oflow-rescue)] [(and (bf> cond-no cond-thres) (bf> (bfabs x) cond-thres)) (mark-erroneous! subexpr 'sensitivity)] [(and (bf> cond-no cond-thres) (bf> cot-x cond-thres)) (mark-erroneous! subexpr 'cancellation)] [(and (bf> cond-no maybe-cond-thres) (bf> (bfabs x) maybe-cond-thres)) (mark-maybe! subexpr 'sensitivity)] [(and (bf> cond-no maybe-cond-thres) (bf> cot-x maybe-cond-thres)) (mark-maybe! subexpr 'cancellation)] [else #f])] [(list (or 'cos.f64 'cos.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define cond-no (bfabs (bf* x (bftan x)))) (cond [(and (bfinfinite? x) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'oflow-rescue)] [(bf> cond-no cond-thres) (mark-erroneous! subexpr 'sensitivity)] [(bf> cond-no maybe-cond-thres) (mark-maybe! subexpr 'sensitivity)] [else #f])] [(list (or 'tan.f64 'tan.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define tot-x (bfabs (bf+ (bfcot x) (bftan x)))) (define cond-no (bf* (bfabs x) tot-x)) (cond [(and (bfinfinite? x) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'oflow-rescue)] [(and (bf> cond-no cond-thres) (bf> (bfabs x) cond-thres)) (mark-erroneous! subexpr 'sensitivity)] [(and (bf> cond-no cond-thres) (bf> tot-x cond-thres)) (mark-erroneous! subexpr 'cancellation)] [(and (bf> cond-no maybe-cond-thres) (bf> (bfabs x) maybe-cond-thres)) (mark-maybe! subexpr 'sensitivity)] [(and (bf> cond-no maybe-cond-thres) (bf> tot-x maybe-cond-thres)) (mark-maybe! subexpr 'cancellation)] [else #f])] [(list (or 'sqrt.f64 'sqrt.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (cond ;; Underflow rescue: [(and (bfzero? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'uflow-rescue)] ;; Overflow rescue: [(and (bfinfinite? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'oflow-rescue)])] [(list (or 'cbrt.f64 'cbrt.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (cond ;; Underflow rescue: [(and (bfzero? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'uflow-rescue)] ;; Overflow rescue: [(and (bfinfinite? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'oflow-rescue)])] [(list (or '/.f64 '/.f32) x-ex y-ex) #:when (or (list? x-ex) (list? y-ex)) (define x (exacts-ref x-ex)) (define y (exacts-ref y-ex)) (cond ;; if the numerator underflows and the denominator: ;; - underflows, nan could be rescued [(and (bfzero? x) (bfzero? y) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'u/u)] ;; - is small enough, 0 underflow could be rescued [(and (bfzero? x) (not (bfzero? subexpr-val))) (mark-erroneous! subexpr 'u/n)] ;; - overflows, no rescue is possible ;; if the numerator overflows and the denominator: ;; - overflows, nan could be rescued [(and (bfinfinite? x) (bfinfinite? y) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'o/o)] ;; - is large enough, inf overflow can be rescued [(and (bfinfinite? x) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'o/n)] ;; - underflow, no rescue is possible ;; if the numerator is normal and the denominator: ;; - overflows, then a rescue is possible [(and (bfinfinite? y) (not (bfzero? subexpr-val))) (mark-erroneous! subexpr 'n/o)] ;; - underflows, then a rescue is possible [(and (bfzero? y) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'n/u)] ;; - is normal, then no rescue is possible [else #f])] [(list (or '*.f64 '*.f32) x-ex y-ex) #:when (or (list? x-ex) [list? y-ex]) (define x (exacts-ref x-ex)) (define y (exacts-ref y-ex)) (cond ;; if one operand underflows and the other overflows, then nan must ;; be rescued. [(and (bfinfinite? x) (bfzero? y) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'o*u)] [(and (bfzero? x) (bfinfinite? y) (not (bfnan? subexpr-val))) (mark-erroneous! subexpr 'u*o)] ;; If one operand is normal and the other overflows then, inf rescue ;; could occur [(and (or (bfinfinite? x) (bfinfinite? y)) (not (bfinfinite? subexpr-val))) (mark-erroneous! subexpr 'n*o)] [(and (or (bfzero? x) (bfzero? y)) (not (bfzero? subexpr-val))) (mark-erroneous! subexpr 'n*u)] ;; If both normal then no error [else #f])] [(list (or 'log.f64 'log.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define cond-num (bfabs (bf/ 1.bf subexpr-val))) (cond ; Condition number hallucination: ; Condition number is high when x = 1, ; but x is exactly 1, so there is no error ; [(and (bf= x 1.bf) (bfzero? subexpr-val)) #f] ; overflow rescue: [(bfinfinite? x) (mark-erroneous! subexpr 'oflow-rescue)] ; underflow rescue: [(bfzero? x) (mark-erroneous! subexpr 'uflow-rescue)] ; High Condition Number: ; CN(log, x) = |1 / log(x)| [(bf> cond-num cond-thres) (mark-erroneous! subexpr 'sensitivity)] [(bf> cond-num maybe-cond-thres) (mark-maybe! subexpr 'sensitivity)] [else #f])] [(list (or 'exp.f64 'exp.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define exp-x (bigfloat->flonum (bfexp x))) (cond ; Condition Number Hallucination: ; When x is large enough that exp(x) overflows, ; condition number is also high. [(and (infinite? exp-x) (bfinfinite? subexpr-val)) #f] ; Condition Number Hallucination: ; When x is large enough (negative) that exp(x) ; underflows, condition number is also high [(and (zero? exp-x) (bfzero? subexpr-val)) #f] ; High Condition Number: ; CN(exp, x) = |x| [(bf> (bfabs x) cond-thres) (mark-erroneous! subexpr 'sensitivity)] [(bf> (bfabs x) maybe-cond-thres) (mark-maybe! subexpr 'sensitivity)] [else #f])] ; FIXME need to rework from scratch [(list (or 'pow.f64 'pow.f32) x-ex y-ex) #:when (or (list? x-ex) (list? y-ex)) (define x (exacts-ref x-ex)) (define y (exacts-ref y-ex)) (define x^y (bigfloat->flonum (bfexpt x y))) (define cond-x (bfabs y)) (define cond-y (bfabs (bf* y (bflog x)))) (cond ;; Hallucination: ;; x has a large exponent and y is 1. The ylogx is large but there is ;; no error because the answer is exactly x ;; [(and (bf= y 1.bf) ;; (bf= x subexpr-val)) #f] ;; Hallucination: ;; y is large but x is exactly 1 ;; [(and (= (bigfloat->flonum x) 1.0) ;; (= (bigfloat->flonum subexpr-val) 1.0)) ;; #f] ;; Hallucination: ;; y is large but x is zero [(and (bfzero? x) (bfzero? subexpr-val)) #f] ;; Hallucination: ;; if x is large enough that x^y overflows, the condition number also ;; is very large, but the answer correctly overflows [(and (bf> y 1.bf) (infinite? x^y) (bfinfinite? subexpr-val)) #f] ;; if x is small enough and y is large enough that x^y underflows, ;; the condition number also gets very large, but the answer ;; correctly underflows [(and (bf> y 1.bf) (zero? x^y) (bfzero? subexpr-val)) #f] [(and (bf< y -1.bf) (zero? x^y) (bfzero? subexpr-val)) #f] [(and (bf< y -1.bf) (infinite? x^y) (bfinfinite? subexpr-val)) #f] [(and (bfzero? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'uflow-rescue)] [(and (bfinfinite? x) (not (bf= subexpr-val x))) (mark-erroneous! subexpr 'oflow-rescue)] [(and (or (bf> cond-x cond-thres) (bf> cond-y cond-thres)) (not (constant? y-ex))) (mark-erroneous! subexpr 'sensitivity)] [(and (or (bf> cond-x maybe-cond-thres) (bf> cond-y maybe-cond-thres)) (not (constant? y-ex))) (mark-maybe! subexpr 'sensitivity)] [else #f])] [(list (or 'acos.f64 'acos.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define cond-x (bfabs (bf/ x (bf* (bfsqrt (bf- 1.bf (bf* x x))) subexpr-val)))) (cond ; Condition number hallucinations: ; acos(1) == 0 ;; [(and (bf= x 1.bf) (bfzero? subexpr-val)) #f] ; acos(-1) == pi ;; [(bf= x -1.bf) #f] ; High Condition Number: ; CN(acos, x) = |x / (√(1 - x^2)acos(x))| [(bf> cond-x cond-thres) (mark-erroneous! subexpr 'sensitivity)] [(bf> cond-x maybe-cond-thres) (mark-maybe! subexpr 'sensitivity)] [else #f])] [(list (or 'asin.f64 'asin.f32) x-ex) #:when (list? x-ex) (define x (exacts-ref x-ex)) (define cond-x (bfabs (bf/ x (bf* (bfsqrt (bf- 1.bf (bf* x x))) subexpr-val)))) (cond ; Condition Number hallucinations: ; asin(1) == pi/2 ;; [(bf= (bfabs x) 1.bf) #f] ;; [(and (bfzero? x) (bfzero? subexpr-val)) #f] ; High Condition Number: ; CN(acos, x) = |x / (√(1 - x^2)asin(x))| [(bf> cond-x cond-thres) (mark-erroneous! subexpr 'sensitivity)] [(bf> cond-x maybe-cond-thres) (mark-maybe! subexpr 'sensitivity)] [else #f])] [_ #f]))) (values error-count-hash expls->points maybe-expls->points oflow-hash uflow-hash)) (define (generate-timelines expr ctx pctx error-count-hash expls->points maybe-expls->points oflow-hash uflow-hash) (define tcount-hash (actual-errors expr ctx pctx)) (define repr (repr-of expr ctx)) (define (values->json vs repr) (map (lambda (value) (value->json value repr)) (vector->list vs))) (define fperrors (for/list ([subexpr (in-list (set-union (hash-keys tcount-hash) (hash-keys error-count-hash)))]) (define pset (hash-ref error-count-hash subexpr '())) (define tset (hash-ref tcount-hash subexpr '())) (define opred (set-subtract pset tset)) (define upred (set-subtract tset pset)) (list (~a subexpr) (length tset) (length opred) (and (not (empty? opred)) (values->json (first opred) repr)) (length upred) (and (not (empty? upred)) (values->json (first upred) repr))))) (define true-error-hash (for/hash ([(key _) (in-pcontext pctx)] [value (in-flvector (errors expr pctx ctx))]) (values key value))) (define explanations-table (for/list ([(key val) (in-dict expls->points)] #:unless (empty? val)) (define subexpr (car key)) (define expl (cdr key)) (define err-count (length val)) (define maybe-count (length (hash-ref maybe-expls->points key '()))) (define flow-list (make-flow-table oflow-hash uflow-hash subexpr expl)) (define locations (get-locations expr subexpr)) (list (~a (car subexpr)) (~a subexpr) (~a expl) err-count maybe-count flow-list locations))) (define sorted-explanations-table (take-top-n (sort explanations-table > #:key fourth))) (define (expls-to-points expls->points) (define expls-points-list (hash->list expls->points)) (define sorted-list (sort expls-points-list > #:key (lambda (x) (length (rest x))))) (define points-per-expl-test (map rest sorted-list)) (define top-3 (take-top-n points-per-expl-test)) (define points-err (apply set-union '() top-3)) (for/hash ([point (in-list points-err)]) (values point true))) (define predicted-total-error (expls-to-points expls->points)) (define maybe-predicted-total-error (expls-to-points maybe-expls->points)) (define confusion-matrix (calculate-confusion true-error-hash predicted-total-error pctx)) (define maybe-confusion-matrix (calculate-confusion-maybe true-error-hash predicted-total-error maybe-predicted-total-error pctx)) (define has-any-error? (for/or ([(pt _) (in-pcontext pctx)]) (> (hash-ref true-error-hash pt) 16))) (define predicted-any-error? (for/or ([(pt _) (in-pcontext pctx)]) (hash-ref predicted-total-error pt false))) (define maybe-any-error? (for/or ([(pt _) (in-pcontext pctx)]) (hash-ref maybe-predicted-total-error pt false))) (define total-confusion-matrix (list (if (and has-any-error? predicted-any-error?) 1 0) (if (and has-any-error? (not predicted-any-error?) maybe-any-error?) 1 0) (if (and has-any-error? (not predicted-any-error?) (not maybe-any-error?)) 1 0) (if (and (not has-any-error?) predicted-any-error?) 1 0) (if (and (not has-any-error?) (not predicted-any-error?) maybe-any-error?) 1 0) (if (and (not has-any-error?) (not predicted-any-error?) (not maybe-any-error?)) 1 0))) (define points->expl (make-hash)) (for* ([points (in-dict-values expls->points)] [pt (in-list points)]) (hash-update! points->expl pt add1 0)) (define freqs (make-hash)) (for ([(pt _) (in-pcontext pctx)]) (define freq (hash-ref points->expl pt 0)) (hash-update! freqs freq add1 0)) (values fperrors sorted-explanations-table confusion-matrix maybe-confusion-matrix total-confusion-matrix freqs)) (define (explain expr ctx pctx) (define-values (subexprs-list repr-hash subexprs-fn) (compile-expr expr ctx)) (define-values (error-count-hash expls->points maybe-expls->points oflow-hash uflow-hash) (predict-errors ctx pctx subexprs-list repr-hash subexprs-fn)) (generate-timelines expr ctx pctx error-count-hash expls->points maybe-expls->points oflow-hash uflow-hash)) (define (flow-list flow-hash expr type) (for/list ([(k v) (in-dict (hash-ref flow-hash expr))]) (list (~a k) type v))) (define (make-flow-table oflow-hash uflow-hash expr expl) (match (list expl expr) [(list 'oflow-rescue _) (flow-list oflow-hash expr "overflow")] [(list 'uflow-rescue _) (flow-list uflow-hash expr "underflow")] [(list 'u/u (list _ num den)) (append (flow-list uflow-hash num "underflow") (flow-list uflow-hash den "underflow"))] [(list 'u/n (list _ num _)) (flow-list uflow-hash num "underflow")] [(list 'o/o (list _ num den)) (append (flow-list oflow-hash num "overflow") (flow-list oflow-hash den "overflow"))] [(list 'o/n (list _ num _)) (flow-list oflow-hash num "overflow")] [(list 'n/o (list _ _ den)) (flow-list oflow-hash den "overflow")] [(list 'n/u (list _ _ den)) (flow-list uflow-hash den "underflow")] [(list 'o*u (list _ left right)) (append (flow-list oflow-hash left "overflow") (flow-list uflow-hash right "underflow"))] [(list 'u*o (list _ left right)) (append (flow-list uflow-hash left "underflow") (flow-list oflow-hash right "overflow"))] [(list 'nan-rescue (list _ left right)) (append (flow-list oflow-hash left "overflow") (flow-list oflow-hash right "overflow"))] [(list 'oflow-left (list left _)) (flow-list oflow-hash left "overflow")] [(list 'oflow-right (list _ right)) (flow-list oflow-hash right "overflow")] [_ '()])) (define (calculate-confusion actual-error predicted-error pcontext) (define outcomes (for/list ([(pt _) (in-pcontext pcontext)]) (define error-actual? (> (hash-ref actual-error pt) 16)) (define error-predicted? (hash-ref predicted-error pt false)) #;(when (and error-predicted? (not error-actual?)) (eprintf "~a\n" pt)) (cons error-actual? error-predicted?))) (define groups (group-by identity outcomes)) (define counts (for/hash ([group (in-list groups)]) (values (first group) (length group)))) (define true-pos (hash-ref counts '(#t . #t) 0)) (define false-pos (hash-ref counts '(#f . #t) 0)) (define true-neg (hash-ref counts '(#f . #f) 0)) (define false-neg (hash-ref counts '(#t . #f) 0)) (list true-pos false-neg false-pos true-neg)) (define (calculate-confusion-maybe actual-error predicted-error maybe-error pcontext) (define outcomes (for/list ([(pt _) (in-pcontext pcontext)]) (define error-actual? (> (hash-ref actual-error pt) 16)) (define error-predicted? (hash-ref predicted-error pt false)) (define maybe-error-predicted? (hash-ref maybe-error pt false)) (cons error-actual? (cond [error-predicted? 'true] [maybe-error-predicted? 'maybe] [else 'false])))) (define groups (group-by identity outcomes)) (define counts (for/hash ([group (in-list groups)]) (values (first group) (length group)))) (define true-pos (hash-ref counts '(#t . true) 0)) (define false-pos (hash-ref counts '(#f . true) 0)) (define false-neg (hash-ref counts '(#t . false) 0)) (define true-neg (hash-ref counts '(#f . false) 0)) (define true-maybe (hash-ref counts '(#t . maybe) 0)) (define false-maybe (hash-ref counts '(#f . maybe) 0)) (list true-pos true-maybe false-neg false-pos false-maybe true-neg)) ================================================ FILE: src/core/localize.rkt ================================================ #lang racket (require math/bigfloat math/flonum racket/hash) (require "../utils/common.rkt" "../syntax/float.rkt" "../syntax/sugar.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "../syntax/platform.rkt" "../syntax/batch.rkt" "compiler.rkt" "points.rkt" "programs.rkt" "../syntax/rival.rkt") (module+ test (require rackunit "../syntax/syntax.rkt" "../syntax/sugar.rkt")) (provide compute-local-errors eval-progs-real local-error-as-tree) (define (eval-progs-real batch brfs ctxs) (define compiler (make-real-compiler batch brfs ctxs)) (define bad-pt (for/list ([ctx* (in-list ctxs)]) ((representation-bf->repr (context-repr ctx*)) +nan.bf))) (define ( pt) (define-values (_ exs) (real-apply compiler pt)) (or exs bad-pt)) ) ;; The local error of an expression f(x, y) is ;; ;; R[f(x, y)] - f(R[x], R[y]) ;; ;; where the `-` is interpreted as ULP difference and `E` means ;; exact real evaluation rounded to target repr. ;; ;; Local error is high when `f` is highly sensitive to rounding error ;; in its inputs `x` and `y`. (define (local-error exact node ulps get-exact) (match node [(? literal?) 1] [(? symbol?) 1] [(approx _ impl) (ulps exact (get-exact impl))] [`(if ,c ,ift ,iff) 1] [(list f args ...) (define argapprox (map get-exact args)) (define approx (apply (impl-info f 'fl) argapprox)) (ulps exact approx)])) (define (make-matrix roots pcontext) (for/vector #:length (vector-length roots) ([node (in-vector roots)]) (make-vector (pcontext-length pcontext)))) ; Compute local error or each sampled point at each node in `prog`. (define (compute-local-errors subexprss ctx pcontext) (define exprs-list (append* subexprss)) ; unroll subexprss (define reprs-list (map (curryr repr-of ctx) exprs-list)) (define ulps-list (map repr-ulps reprs-list)) (define ctx-list (for/list ([subexpr (in-list exprs-list)] [repr (in-list reprs-list)]) (struct-copy context ctx [repr repr]))) (define-values (expr-batch brfs) (progs->batch exprs-list)) (define roots (list->vector (map batchref-idx brfs))) (define-values (spec-batch spec-brfs) (progs->batch (map prog->spec exprs-list))) (define subexprs-fn (eval-progs-real spec-batch spec-brfs ctx-list)) (define errs (make-matrix roots pcontext)) (for ([(pt ex) (in-pcontext pcontext)] [pt-idx (in-naturals)]) (define exacts (list->vector (subexprs-fn pt))) (define (get-exact brf) (vector-ref exacts (vector-member (batchref-idx brf) roots))) (for ([expr (in-list exprs-list)] [brf brfs] [ulps (in-list ulps-list)] [exact (in-vector exacts)] [expr-idx (in-naturals)]) (define err (local-error exact (deref brf) ulps get-exact)) (vector-set! (vector-ref errs expr-idx) pt-idx err))) (define n 0) (for/list ([subexprs (in-list subexprss)]) (for*/hash ([subexpr (in-list subexprs)]) (begin0 (values subexpr (vector->list (vector-ref errs n))) (set! n (add1 n)))))) ;; The absolute error of expression `e` is R[e - R[e]]. ;; However, it's possible that R[e] is infinity or NaN; ;; in this case, computing the absolute error won't work ;; since those aren't real numbers. To fix this, we replace all ;; non-finite R[e] with 0. (define (remove-infinities pt reprs) (for/vector ([val (in-vector pt)] [repr (in-list reprs)]) (define bf-val ((representation-repr->bf repr) val)) (if (implies (bigfloat? bf-val) (bfrational? bf-val)) val ((representation-bf->repr repr) 0.bf)))) ;; Compute local error or each sampled point at each node in `prog`. (define (compute-errors subexprss ctx pcontext) ;; We compute the actual (float) result (define exprs-list (append* subexprss)) ; unroll subexprss (define actual-value-fn (compile-progs exprs-list ctx)) ;; And the real result (define spec-list (map prog->spec exprs-list)) (define reprs-list (map (curryr repr-of ctx) exprs-list)) (define ulps-list (map repr-ulps reprs-list)) (define ctx-list (for/list ([subexpr (in-list exprs-list)] [repr (in-list reprs-list)]) (struct-copy context ctx [repr repr]))) (define-values (spec-batch spec-brfs) (progs->batch spec-list)) (define subexprs-fn (eval-progs-real spec-batch spec-brfs ctx-list)) ;; And the absolute difference between the two (define exact-var-names (for/list ([expr (in-list exprs-list)]) (gensym 'exact))) (define delta-ctx (context (append (context-vars ctx) exact-var-names) (get-representation 'binary64) (append (context-var-reprs ctx) reprs-list))) (define compare-specs (for/list ([spec (in-list spec-list)] [expr (in-list exprs-list)] [repr (in-list reprs-list)] [var (in-list exact-var-names)]) (match (representation-type repr) ['bool 0] ; We can't subtract booleans so ignore them ['real `(fabs (- ,spec ,var))] [_ 0]))) (define-values (compare-batch compare-brfs) (progs->batch compare-specs)) (define delta-fn (eval-progs-real compare-batch compare-brfs (map (const delta-ctx) compare-specs))) (define-values (expr-batch brfs) (progs->batch exprs-list)) (define roots (list->vector (map batchref-idx brfs))) (define ulp-errs (make-matrix roots pcontext)) (define exacts-out (make-matrix roots pcontext)) (define approx-out (make-matrix roots pcontext)) (define true-error-out (make-matrix roots pcontext)) (define spec-vec (list->vector spec-list)) (define ctx-vec (list->vector ctx-list)) (for ([(pt ex) (in-pcontext pcontext)] [pt-idx (in-naturals)]) (define exacts (list->vector (subexprs-fn pt))) (define (get-exact brf) (vector-ref exacts (vector-member (batchref-idx brf) roots))) (define actuals (actual-value-fn pt)) (define pt* (vector-append pt (remove-infinities actuals reprs-list))) (define deltas (list->vector (delta-fn pt*))) (for ([ulps (in-list ulps-list)] [brf brfs] [exact (in-vector exacts)] [actual (in-vector actuals)] [delta (in-vector deltas)] [expr-idx (in-naturals)]) (define ulp-err (local-error exact (deref brf) ulps get-exact)) (vector-set! (vector-ref exacts-out expr-idx) pt-idx exact) (vector-set! (vector-ref approx-out expr-idx) pt-idx actual) (vector-set! (vector-ref ulp-errs expr-idx) pt-idx ulp-err) (vector-set! (vector-ref true-error-out expr-idx) pt-idx delta))) (define n 0) (for/list ([subexprs (in-list subexprss)]) (for*/hash ([subexpr (in-list subexprs)]) (begin0 (values subexpr (hasheq 'ulp-errs (vector->list (vector-ref ulp-errs n)) 'exact-values (vector->list (vector-ref exacts-out n)) 'approx-values (vector->list (vector-ref approx-out n)) 'absolute-error (vector->list (vector-ref true-error-out n)))) (set! n (add1 n)))))) (define (expr-fpcore-operator expr ctx) (match (prog->fpcore expr ctx) [(list '! props ... (list op args ...)) op] [(list op args ...) op] [(? number? c) (exact->inexact c)] [(? symbol? c) c])) (define (expr->json-tree expr ctx decorate) (define (make-json-tree subexpr) (define args (if (list? subexpr) (rest subexpr) '())) (hash-union (hasheq 'e (~s (expr-fpcore-operator subexpr ctx)) 'children (map make-json-tree args)) (decorate subexpr))) (make-json-tree expr)) ;; Compute the local error of every subexpression of `prog` ;; and returns the error information as an S-expr in the ;; same shape as `prog` (define (local-error-as-tree expr ctx pcontext) (define data-hash (first (compute-errors (list (all-subexpressions expr)) ctx pcontext))) (define (translate-booleans value) (match value [#t 'true] [#f 'false] [v v])) (define (expr-data expr) (define data (hash-ref data-hash expr)) (define abs-error (~s (first (hash-ref data 'absolute-error)))) (define ulp-error (~s (ulps->bits (first (hash-ref data 'ulp-errs))))) ; unused by Odyssey (define ulp-errs (hash-ref data 'ulp-errs)) (define avg-error (format-bits (errors-score (for/flvector #:length (length ulp-errs) ([err (in-list ulp-errs)]) (ulps->bits err))))) (define exact-error (~s (translate-booleans (first (hash-ref data 'exact-values))))) (define actual-error (~s (translate-booleans (first (hash-ref data 'approx-values))))) (define percent-accurate (cond [(nan? (first (hash-ref data 'absolute-error))) 'invalid] ; HACK: should specify if invalid or unsamplable [else (define repr (repr-of expr ctx)) (define total-bits (representation-total-bits repr)) (define bits-error (ulps->bits (first (hash-ref data 'ulp-errs)))) (* 100 (- 1 (/ bits-error total-bits)))])) (hasheq 'ulps-error ulp-error 'avg-error avg-error 'exact-value exact-error 'actual-value actual-error 'percent-accuracy (~s percent-accurate) 'abs-error-difference (match (first (hash-ref data 'absolute-error)) [(? zero?) "equal"] [(? nan?) "invalid"] [_ abs-error]))) (expr->json-tree expr ctx expr-data)) ================================================ FILE: src/core/mainloop.rkt ================================================ #lang racket (require "../config.rkt" "../core/alternative.rkt" "../utils/common.rkt" "../utils/timeline.rkt" "../syntax/platform.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "alt-table.rkt" "bsearch.rkt" "../syntax/batch.rkt" "derivations.rkt" "patch.rkt" "points.rkt" "compiler.rkt" "preprocess.rkt" "programs.rkt" "regimes.rkt" "batch-reduce.rkt") (provide run-improve! sort-alts) ;; The Herbie main loop goes through a simple iterative process: ;; ;; - Choose all unfinished candidates ;; - Generating new candidates based on them ;; - Evaluate all the new and old candidates and prune to the best ;; ;; Each stage is stored in this global variable for REPL debugging. (define/reset ^table^ #f) ;; Starting program for the current run (define *start-brf* (make-parameter #f)) (define *pcontext* (make-parameter #f)) (define *preprocessing* (make-parameter '())) (define *global-batch* (make-parameter #f)) ;; These high-level functions give the high-level workflow of Herbie: ;; - Initial steps: explain, preprocessing, initialize the alt table ;; - the loop: choose some alts, localize, run the patch table, and finalize ;; - Final steps: regimes, derivations, and remove preprocessing (define (run-improve! initial specification context pcontext) (timeline-event! 'preprocess) (define preprocessing (if (flag-set? 'setup 'preprocess) (find-preprocessing specification context) '())) (timeline-push! 'symmetry (map ~a preprocessing)) (define pcontext* (preprocess-pcontext context pcontext preprocessing)) (*pcontext* pcontext*) (parameterize ([*global-batch* (batch-empty)]) (define global-spec-batch (batch-empty)) (define spec-reducer (batch-reduce global-spec-batch)) (*preprocessing* preprocessing) (define initial-brf (batch-add! (*global-batch*) initial)) (*start-brf* initial-brf) (define start-alt (alt initial-brf 'start '())) (^table^ (make-alt-table (*global-batch*) pcontext start-alt context)) (for ([_ (in-range (*num-iterations*))] #:break (atab-completed? (^table^))) (run-iteration! global-spec-batch spec-reducer)) (define alternatives (extract!)) (timeline-event! 'preprocess) (for/list ([altn alternatives]) (define expr (alt-expr altn)) (define expr* (compile-useful-preprocessing expr context pcontext (*preprocessing*))) (alt expr* 'add-preprocessing (list altn))))) (define (extract!) (timeline-push-alts! '()) (define all-alts (atab-all-alts (^table^))) (define joined-alts (make-regime! (*global-batch*) all-alts (*start-brf*))) (define annotated-alts (add-derivations! joined-alts)) (define unbatched-alts (unbatchify-alts (*global-batch*) annotated-alts)) (timeline-push! 'stop (if (atab-completed? (^table^)) "done" "fuel") 1) (map car (sort-alts unbatched-alts))) ;; The rest of the file is various helper / glue functions used by ;; Herbie. These often wrap other Herbie components, but add logging ;; and timeline data. (define (dump-intermediates! batch altns) (define dump-dir "dump-intermediates") (unless (directory-exists? dump-dir) (make-directory dump-dir)) (define name (for/first ([i (in-naturals)] #:unless (file-exists? (build-path dump-dir (format "~a.rktd" i)))) (build-path dump-dir (format "~a.rktd" i)))) (define exprs (batch-exprs batch)) (call-with-output-file name #:exists 'replace (lambda (out) (for ([altn (in-list altns)]) (writeln (exprs (alt-expr altn)) out))))) (define (batch-score-alts altns) (map errors-score (batch-errors (*global-batch*) (map alt-expr altns) (*pcontext*) (*context*)))) (define (timeline-push-alts! next-alts) (define pending-alts (atab-not-done-alts (^table^))) (define active-alts (atab-active-alts (^table^))) (define scores (batch-score-alts active-alts)) (define batch-jsexpr (batch->jsexpr (*global-batch*) (map alt-expr active-alts))) (define roots (hash-ref batch-jsexpr 'roots)) (define repr (context-repr (*context*))) (timeline-push! 'batch batch-jsexpr) (for ([alt (in-list active-alts)] [score (in-list scores)] [root (in-list roots)]) (timeline-push! 'alts root (cond [(set-member? next-alts alt) "next"] [(set-member? pending-alts alt) "fresh"] [else "done"]) score (~a (representation-name repr))))) (define (set-intersect-size keys set) (for/sum ([key (in-list keys)] #:when (set-member? set key)) 1)) (define (expr-recurse-impl expr f) (match expr [(approx _ impl) (f impl)] [_ (expr-recurse expr f)])) ;; Converts a patch to full alt with valid history (define (reconstruct! starting-alts new-alts) (timeline-event! 'reconstruct) (define (group-equivalent-alts alts) (define fn (compile-batch (*global-batch*) (map alt-expr alts) (*context*))) (define signatures (make-vector (length alts) '())) (define batch-cost (alt-batch-costs (*global-batch*) (*context*))) (for ([pt (in-vector (pcontext-points (*pcontext*)))]) (define outs (fn pt)) (for ([out (in-vector outs)] [idx (in-naturals)]) (vector-set! signatures idx (cons out (vector-ref signatures idx))))) (define (best-alt alt1 alt2) (define cost1 (batch-cost (alt-expr alt1))) (define cost2 (batch-cost (alt-expr alt2))) (if (or (< cost1 cost2) (and (= cost1 cost2) (exprseteq (atab-not-done-alts (^table^)))) (define final-active-set (list->seteq (atab-active-alts (^table^)))) (define final-done-set (set-subtract final-active-set final-fresh-set)) (timeline-push! 'count (+ (length patched) (length orig-fresh-alts) (length orig-done-alts)) (+ (set-count final-fresh-set) (set-count final-done-set))) (define data (hash 'new (list (length patched) (set-intersect-size patched final-fresh-set)) 'fresh (list (length orig-fresh-alts) (set-intersect-size orig-fresh-alts final-fresh-set)) 'done (list (- (length orig-done-alts) (length picked-alts)) (- (set-intersect-size orig-done-alts final-done-set) (set-intersect-size picked-alts final-done-set))) 'picked (list (length picked-alts) (set-intersect-size picked-alts final-done-set)))) (timeline-push! 'kept data) (define repr (context-repr (*context*))) (timeline-push! 'min-error (errors-score (atab-min-errors (^table^))) (format "~a" (representation-name repr))) (void)) (define (run-iteration! global-spec-batch spec-reducer) (define pending-alts (atab-not-done-alts (^table^))) (timeline-push-alts! pending-alts) (^table^ (atab-set-picked (^table^) pending-alts)) (define brfs (map alt-expr pending-alts)) (define brfs* (batch-reachable (*global-batch*) brfs #:condition node-is-impl?)) (define results (generate-candidates (*global-batch*) brfs* global-spec-batch spec-reducer)) (define patched (reconstruct! pending-alts results)) (finalize-iter! pending-alts patched) (void)) (define (make-regime! batch alts start-prog) (define ctx (*context*)) (define repr (context-repr ctx)) (define alt-costs (alt-batch-costs batch ctx)) (cond [(and (flag-set? 'reduce 'regimes) (> (length alts) 1) (equal? (representation-type repr) 'real) (not (null? (context-vars ctx))) (get-fpcore-impl 'if '() (list repr repr)) (get-fpcore-impl '<= '() (list repr repr))) (define opts (pareto-regimes batch (sort alts < #:key (compose alt-costs alt-expr)) start-prog ctx (*pcontext*))) (for/list ([opt (in-list opts)]) (match-define (option splitindices opt-alts _ brf) opt) (timeline-event! 'bsearch) (define exprs (batch-exprs batch)) (define branch-expr (exprs brf)) (define use-binary? (and (flag-set? 'reduce 'binary-search) (> (length splitindices) 1) (critical-subexpression? (exprs start-prog) branch-expr) (for/and ([alt (in-list opt-alts)]) (critical-subexpression? (exprs (alt-expr alt)) branch-expr)))) (cond [(= (length splitindices) 1) (list-ref opt-alts (si-cidx (first splitindices)))] [use-binary? (combine-alts/binary batch opt start-prog ctx (*pcontext*))] [else (combine-alts batch opt ctx)]))] [else (define scores (batch-score-alts alts)) (list (cdr (argmin car (map (λ (a s) (cons s a)) alts scores))))])) (define (add-derivations! alts) (cond [(flag-set? 'generate 'proofs) (timeline-event! 'derivations) (add-derivations alts)] [else alts])) (define (sort-alts alts [errss (exprs-errors (map alt-expr alts) (*pcontext*) (*context*))]) ;; sort everything by error + cost (define repr (context-repr (*context*))) (define alts-to-be-sorted (map cons alts errss)) (sort alts-to-be-sorted (lambda (x y) (or (< (errors-score (cdr x)) (errors-score (cdr y))) ; sort by error (and (equal? (errors-score (cdr x)) (errors-score (cdr y))) ; if error is equal sort by cost (< (alt-cost (car x) repr) (alt-cost (car y) repr))))))) ================================================ FILE: src/core/patch.rkt ================================================ #lang racket (require "../syntax/platform.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "../core/alternative.rkt" "../utils/common.rkt" "../syntax/float.rkt" "../utils/timeline.rkt" "../syntax/batch.rkt" "egg-herbie.rkt" "egglog-herbie.rkt" "programs.rkt" "rules.rkt" "../syntax/rival.rkt" "taylor.rkt") (provide generate-candidates get-starting-expr) ;;;;;;;;;;;;;;;;;;;;;;;;;;;; Taylor ;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define transforms-to-try (let ([invert-x (λ (x) `(/ 1 ,x))] [exp-x (λ (x) `(exp ,x))] [log-x (λ (x) `(log ,x))] [ninvert-x (λ (x) `(/ 1 (neg ,x)))]) `((0 ,identity ,identity) (inf ,invert-x ,invert-x) (-inf ,ninvert-x ,ninvert-x) #;(exp ,exp-x ,log-x) #;(log ,log-x ,exp-x)))) (define (taylor-alts altns global-batch spec-batch reducer) (define vars (for/list ([var (in-list (context-vars (*context*)))] #:when (equal? (representation-type (context-lookup (*context*) var)) 'real)) var)) (define brfs (map alt-expr altns)) (define reprs (map (batch-reprs global-batch (*context*)) brfs)) ;; Specs (define spec-brfs (batch-to-spec! global-batch brfs)) ;; These specs will go into (approx spec impl) (define free-vars (map (batch-free-vars global-batch) spec-brfs)) (define spec-brfs* (map (batch-copy-only! spec-batch global-batch) spec-brfs)) ;; copy from global-batch to spec-batch (define copier (batch-copy-only! global-batch spec-batch)) ;; copy from spec-batch to global-batch (reap [sow] (parameterize ([reduce reducer] ;; reduces over spec-batch [add (λ (x) (batch-add! spec-batch x))]) ;; adds to spec-batch ;; Zero expansion (for ([spec-brf (in-list spec-brfs)] [repr (in-list reprs)] [altn (in-list altns)] #:unless (array-representation? repr)) (define genexpr0 (batch-add! global-batch 0)) (define gen0 (approx spec-brf (hole (representation-name repr) genexpr0))) (define brf0 (batch-add! global-batch gen0)) (sow (alt brf0 `(taylor zero undef-var) (list altn)))) ;; Taylor expansions ;; List> (define taylor-coeffs (taylor-coefficients spec-batch spec-brfs* vars transforms-to-try)) (define idx 0) (for* ([var (in-list vars)] [transform-type transforms-to-try]) (match-define (list name f finv) transform-type) (define timeline-stop! (timeline-start! 'series (~a var) (~a name))) (define taylor-coeffs* (list-ref taylor-coeffs idx)) (define genexprs (approximate taylor-coeffs* spec-batch var #:transform (cons f finv))) (for ([genexpr (in-list genexprs)] [spec-brf (in-list spec-brfs)] [repr (in-list reprs)] [altn (in-list altns)] [fv (in-list free-vars)] #:when (set-member? fv var)) ;; check whether var exists in expr at all (for ([i (in-range (*taylor-order-limit*))]) ;; adding a new expansion to the global batch (define gen (approx spec-brf (hole (representation-name repr) (copier (genexpr))))) (define brf (batch-add! global-batch gen)) (sow (alt brf `(taylor ,name ,var) (list altn))))) (set! idx (add1 idx)) (timeline-stop!))))) (define (run-taylor altns global-batch spec-batch reducer) (timeline-event! 'series) (define (key x) (define expr (deref (alt-expr x))) (if (approx? expr) (approx-impl expr) expr)) (define approxs (remove-duplicates (taylor-alts altns global-batch spec-batch reducer) #:key key)) (define approxs* (remove-duplicates (run-lowering approxs global-batch) #:key key)) (timeline-push! 'inputs (batch->jsexpr global-batch (map alt-expr altns))) (timeline-push! 'outputs (batch->jsexpr global-batch (map alt-expr approxs*))) (timeline-push! 'count (length altns) (length approxs*)) approxs*) (define (run-lowering altns global-batch) (define schedule '(lower)) ; run egg (define brfs (map alt-expr altns)) (define reprs (map (batch-reprs global-batch (*context*)) brfs)) (define runner (cond [(flag-set? 'generate 'egglog) (define batch* (batch-empty)) (define copy-f (batch-copy-only! batch* global-batch)) (define brfs* (map copy-f brfs)) (make-egglog-runner batch* brfs* reprs schedule (*context*))] [else (make-egraph global-batch brfs reprs schedule (*context*))])) (define batchrefss (if (flag-set? 'generate 'egglog) (run-egglog runner global-batch 'taylor #:extract 1) (egraph-best runner global-batch))) ; apply changelists (reap [sow] (for ([batchrefs (in-list batchrefss)] [altn (in-list altns)]) (for ([batchref* (in-list batchrefs)]) (sow (alt batchref* (list 'rr runner #f) (list altn))))))) (define (run-evaluate altns global-batch) (timeline-event! 'sample) (define all-brfs (map alt-expr altns)) (define spec-brfs (batch-to-spec! global-batch all-brfs)) (define free-vars (batch-free-vars global-batch)) (define repr-of (batch-reprs global-batch (*context*))) (define real-pairs (for/list ([altn (in-list altns)] [spec-brf (in-list spec-brfs)] #:when (set-empty? (free-vars spec-brf)) #:unless (literal? (deref (alt-expr altn))) #:when (equal? (representation-type (repr-of (alt-expr altn))) 'real)) (cons altn spec-brf))) (define real-altns (map car real-pairs)) (define real-spec-brfs (map cdr real-pairs)) (define brfs (map alt-expr real-altns)) (define reprs (map repr-of brfs)) (define contexts (for/list ([repr (in-list reprs)]) (context '() repr '()))) (define-values (status pts) (if (null? real-spec-brfs) (values 'invalid #f) (let ([real-compiler (make-real-compiler global-batch real-spec-brfs contexts)]) (real-apply real-compiler (vector))))) (define literals (for/list ([pt (in-list (if (equal? status 'valid) pts '()))] [ctx (in-list contexts)] #:when (equal? status 'valid)) (define repr (context-repr ctx)) (literal (repr->real pt repr) (representation-name repr)))) (define final-altns (for/list ([literal (in-list literals)] [altn (in-list real-altns)] #:when (equal? status 'valid)) (define brf (batch-add! global-batch literal)) (alt brf '(evaluate) (list altn)))) (timeline-push! 'inputs (batch->jsexpr global-batch real-spec-brfs)) (timeline-push! 'outputs (map ~a literals)) final-altns) ;;;;;;;;;;;;;;;;;;;;;;;;;;;; Recursive Rewrite ;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (run-rr altns global-batch) (timeline-event! 'rewrite) ; egg schedule (4-phases for mathematical rewrites, sound-X removal, and implementation selection) (define schedule '(lift rewrite unsound lower)) (define brfs (map alt-expr altns)) (define reprs (map (batch-reprs global-batch (*context*)) brfs)) (define runner (cond [(flag-set? 'generate 'egglog) (define batch* (batch-empty)) (define copy-f (batch-copy-only! batch* global-batch)) (define brfs* (map copy-f brfs)) (make-egglog-runner batch* brfs* reprs schedule (*context*))] [else (make-egraph global-batch brfs reprs schedule (*context*))])) (define batchrefss (if (flag-set? 'generate 'egglog) (run-egglog runner global-batch 'rewrite #:extract 1000000) ; "infinity" (egraph-variations runner global-batch))) ; apply changelists (define rewritten (reap [sow] (for ([batchrefs (in-list batchrefss)] [altn (in-list altns)]) (for ([batchref* (in-list batchrefs)]) (sow (alt batchref* (list 'rr runner #f) (list altn))))))) (timeline-push! 'inputs (batch->jsexpr global-batch (map alt-expr altns))) (timeline-push! 'outputs (batch->jsexpr global-batch (map alt-expr rewritten))) (timeline-push! 'count (length altns) (length rewritten)) rewritten) ;;;;;;;;;;;;;;;;;;;;;;;;;;;; Public API ;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (get-starting-expr altn) (match (alt-prevs altn) [(list) (alt-expr altn)] [(list prev) (get-starting-expr prev)])) (define (generate-candidates batch brfs spec-batch reducer) ; Starting alternatives (define start-altns (for/list ([brf brfs]) (alt brf 'patch '()))) (define evaluations (if (flag-set? 'generate 'evaluate) (run-evaluate start-altns batch) '())) ; Series expand (define approximations (if (flag-set? 'generate 'taylor) (run-taylor start-altns batch spec-batch reducer) '())) ; Recursive rewrite (define rewritten (if (flag-set? 'generate 'rr) (run-rr start-altns batch) '())) (remove-duplicates (append evaluations rewritten approximations) #:key (λ (altn) (cons (alt-expr altn) (get-starting-expr altn))))) ================================================ FILE: src/core/points.rkt ================================================ #lang racket (require math/flonum "../syntax/float.rkt" "../syntax/types.rkt" "../syntax/batch.rkt" "compiler.rkt") (provide in-pcontext mk-pcontext for/pcontext pcontext? pcontext-points split-pcontext pcontext-length errors batchref-errors batch-errors exprs-errors errors-score) ;; pcontexts are Herbie's standard data structure for storing ;; ground-truth information. They contain 1) a set of sampled input ;; points; and 2) a ground-truth output for each input. (struct pcontext (points exacts) #:prefab) (define (in-pcontext pcontext) (in-parallel (in-vector (pcontext-points pcontext)) (in-vector (pcontext-exacts pcontext)))) (define (pcontext-length pcontext) (vector-length (pcontext-points pcontext))) (define/contract (mk-pcontext points exacts) (-> (non-empty-listof vector?) (non-empty-listof any/c) pcontext?) (pcontext (list->vector points) (list->vector exacts))) (define-syntax-rule (for/pcontext ([(pt ex) pcontext] other ...) body ...) (let-values ([(pts* exs*) (for/lists (pts* exs*) ([(pt ex) (in-pcontext pcontext)] other ...) body ...)]) (mk-pcontext pts* exs*))) (define (split-pcontext pctx num-a num-b) (match-define (pcontext pts exs) pctx) (unless (= (+ num-a num-b) (vector-length pts)) (error 'split-pcontext "Cannot split pcontext of size ~a into ~a and ~a" (vector-length pts) num-a num-b)) (define-values (pts-a pts-b) (vector-split-at pts num-a)) (define-values (exs-a exs-b) (vector-split-at exs num-a)) (values (pcontext pts-a exs-a) (pcontext pts-b exs-b))) ;; Herbie's standard error measure is the average bits of error across ;; all points in a pcontext. (define (average . s) (/ (apply + s) (length s))) (define (errors-score e) (/ (flvector-sum e) (flvector-length e))) (define (errors expr pcontext ctx) (first (exprs-errors (list expr) pcontext ctx))) (define (batchref-errors brf pcontext ctx) (first (batch-errors (batchref-batch brf) (list brf) pcontext ctx))) (define (exprs-errors exprs pcontext ctx) (define fn (compile-progs exprs ctx)) (define num-exprs (length exprs)) (generate-errors fn pcontext ctx num-exprs)) (define (batch-errors batch brfs pcontext ctx) (define fn (compile-batch batch brfs ctx)) (define num-exprs (length brfs)) (generate-errors fn pcontext ctx num-exprs)) (define (generate-errors fn pcontext ctx num-exprs) (define repr (context-repr ctx)) (define ulps (repr-ulps repr)) (define max-ulps (+ 1 (expt 2 (representation-total-bits repr)))) (define invalid-bits (real->double-flonum (representation-total-bits repr))) (define num-points (pcontext-length pcontext)) (define results (build-vector num-exprs (lambda (_) (make-flvector num-points invalid-bits)))) (for ([point (in-vector (pcontext-points pcontext))] [exact (in-vector (pcontext-exacts pcontext))] [pidx (in-naturals)]) (define outs (fn point)) (for ([out (in-vector outs)] [result (in-vector results)]) (define err-ulps (ulps out exact)) (flvector-set! result pidx (if (= err-ulps max-ulps) invalid-bits (ulps->bits err-ulps))))) (vector->list results)) ================================================ FILE: src/core/preprocess.rkt ================================================ #lang racket (require math/bigfloat) (require "../syntax/platform.rkt" "../syntax/syntax.rkt" "../syntax/types.rkt" "../utils/common.rkt" "../syntax/float.rkt" "../utils/timeline.rkt" "../syntax/batch.rkt" "egg-herbie.rkt" "points.rkt" "programs.rkt" "rules.rkt") (provide find-preprocessing preprocess-pcontext remove-unnecessary-preprocessing compile-preprocessing compile-useful-preprocessing) (define (has-fabs-impl? repr) (get-fpcore-impl 'fabs (repr->prop repr) (list repr))) (define (has-fmin-fmax-impl? repr) (and (get-fpcore-impl 'fmin (repr->prop repr) (list repr repr)) (get-fpcore-impl 'fmax (repr->prop repr) (list repr repr)))) (define (has-copysign-impl? repr) (and (get-fpcore-impl '* (repr->prop repr) (list repr repr)) (get-fpcore-impl 'copysign (repr->prop repr) (list repr repr)))) ;; The even identities: f(x) = f(-x) ;; Requires `neg` and `fabs` operator implementations. (define (make-even-identities spec ctx) (for/list ([var (in-list (context-vars ctx))] [repr (in-list (context-var-reprs ctx))] #:when (has-fabs-impl? repr)) (cons `(abs ,var) (replace-expression spec var `(neg ,var))))) ;; The odd identities: f(x) = -f(-x) ;; Requires `neg` and `fabs` operator implementations. (define (make-odd-identities spec ctx) (for/list ([var (in-list (context-vars ctx))] [repr (in-list (context-var-reprs ctx))] #:when (and (has-fabs-impl? repr) (has-copysign-impl? (context-repr ctx)))) (cons `(negabs ,var) (replace-expression `(neg ,spec) var `(neg ,var))))) ;; Sort identities: f(a, b) = f(b, a) (define (make-sort-identities spec ctx) (define pairs (combinations (context-vars ctx) 2)) (for/list ([pair (in-list pairs)] ;; Can only sort same-repr variables #:when (equal? (context-lookup ctx (first pair)) (context-lookup ctx (second pair))) #:when (has-fmin-fmax-impl? (context-lookup ctx (first pair)))) (match-define (list a b) pair) (cons `(sort ,a ,b) (replace-vars `((,a . ,b) (,b . ,a)) spec)))) ;; See https://pavpanchekha.com/blog/symmetric-expressions.html (define (find-preprocessing expr ctx) (define spec (prog->spec expr)) ;; identities (define identities (append (make-even-identities spec ctx) (make-odd-identities spec ctx) (make-sort-identities spec ctx))) ;; make egg runner (define-values (batch brfs) (progs->batch (cons spec (map cdr identities)))) (define runner (make-egraph batch brfs (make-list (length brfs) (context-repr ctx)) '(rewrite) ctx)) ;; collect equalities (for/list ([(ident spec*) (in-dict identities)] #:when (egraph-equal? runner spec spec*)) ident)) (define (preprocess-pcontext context pcontext preprocessing) (define preprocess (apply compose (map (curry instruction->operator context) ;; Function composition applies the rightmost function first (reverse preprocessing)))) (for/pcontext ([(x y) pcontext]) (preprocess x y))) (define (vector-update v i f) (define copy (make-vector (vector-length v))) (vector-copy! copy 0 v) (vector-set! copy i (f (vector-ref copy i))) copy) (define (vector-set* v indices vals) (define copy (make-vector (vector-length v))) (vector-copy! copy 0 v) (for ([i (in-list indices)] [v (in-list vals)]) (vector-set! copy i v)) copy) (define (instruction->operator context instruction) (define variables (context-vars context)) (define sort* (curryr sort (curryr prop var-repr) (list var-repr)) 'fl)) (lambda (x y) (values (vector-update x index fabs) y))] [(list 'negabs variable) (define index (index-of variables variable)) (define var-repr (context-lookup context variable)) (define repr (context-repr context)) (define fabs (impl-info (get-fpcore-impl 'fabs (repr->prop var-repr) (list var-repr)) 'fl)) (define mul (impl-info (get-fpcore-impl '* (repr->prop repr) (list repr repr)) 'fl)) (define copysign (impl-info (get-fpcore-impl 'copysign (repr->prop repr) (list repr repr)) 'fl)) (define repr1 ((representation-bf->repr repr) 1.bf)) (lambda (x y) (values (vector-update x index fabs) (mul (copysign repr1 (vector-ref x index)) y)))])) ; until fixed point, iterate through preprocessing attempting to drop preprocessing with no effect on error (define (remove-unnecessary-preprocessing expression context pcontext preprocessing #:removed [removed empty]) (define-values (result newly-removed) (let loop ([preprocessing preprocessing] [i 0] [removed removed]) (cond [(>= i (length preprocessing)) (values preprocessing removed)] [(preprocessing-<=? expression context pcontext (drop-at preprocessing i) preprocessing) (loop (drop-at preprocessing i) i (cons (list-ref preprocessing i) removed))] [else (loop preprocessing (+ i 1) removed)]))) (cond [(< (length result) (length preprocessing)) (remove-unnecessary-preprocessing expression context pcontext result #:removed newly-removed)] [else (timeline-push! 'symmetry (map ~a result)) result])) (define (preprocessing-<=? expression context pcontext preprocessing1 preprocessing2) (define pcontext1 (preprocess-pcontext context pcontext preprocessing1)) (define pcontext2 (preprocess-pcontext context pcontext preprocessing2)) (<= (errors-score (errors expression pcontext1 context)) (errors-score (errors expression pcontext2 context)))) (define (compile-preprocessing expression context preprocessing) (match preprocessing ; Not handled yet [(list 'sort a b) (define repr (context-lookup context a)) (define fmin (get-fpcore-impl 'fmin (repr->prop repr) (list repr repr))) (define fmax (get-fpcore-impl 'fmax (repr->prop repr) (list repr repr))) (replace-vars (list (list a fmin a b) (list b fmax a b)) expression)] [(list 'abs var) (define repr (context-lookup context var)) (define fabs (get-fpcore-impl 'fabs (repr->prop repr) (list repr))) (define replacement (list fabs var)) (replace-expression expression var replacement)] [(list 'negabs var) (define repr (context-lookup context var)) (define fabs (get-fpcore-impl 'fabs (repr->prop repr) (list repr))) (define replacement (list fabs var)) (define mul (get-fpcore-impl '* (repr->prop repr) (list repr repr))) (define copysign (get-fpcore-impl 'copysign (repr->prop repr) (list repr repr))) `(,mul (,copysign ,(literal 1 (representation-name repr)) ,var) ,(replace-expression expression var replacement))])) (define (compile-useful-preprocessing expression context pcontext preprocessing) (define useful-preprocessing (remove-unnecessary-preprocessing expression context pcontext preprocessing)) (for/fold ([expr expression]) ([prep (in-list (reverse useful-preprocessing))]) (compile-preprocessing expr context prep))) ================================================ FILE: src/core/programs.rkt ================================================ #lang racket (require "../utils/common.rkt" "../syntax/syntax.rkt" "../syntax/platform.rkt" "../syntax/types.rkt" "../syntax/batch.rkt") (provide expr? expr len-a len-b) 1] [else (let loop ([a a] [b b]) (cond [(null? a) 0] [else (define cmp (expr-cmp (car a) (car b))) (if (zero? cmp) (loop (cdr a) (cdr b)) cmp)]))])] [((? list?) _) 1] [(_ (? list?)) -1] [((? approx?) (? approx?)) (define cmp-spec (expr-cmp (approx-spec a) (approx-spec b))) (if (zero? cmp-spec) (expr-cmp (approx-impl a) (approx-impl b)) cmp-spec)] [((? approx?) _) 1] [(_ (? approx?)) -1] [((? hole?) (? hole?)) (define cmp-spec (expr-cmp (hole-spec a) (hole-spec b))) (if (zero? cmp-spec) (expr-cmp (hole-precision a) (hole-precision b)) cmp-spec)] [((? hole?) _) 1] [(_ (? hole?)) -1] [((? symbol?) (? symbol?)) (cond [(symbol expr? expr? expr? expr?) (let loop ([expr expr]) (match expr [(== from) to] [(? number?) expr] [(? literal?) expr] [(? symbol?) expr] [(approx spec impl) (approx (loop spec) (loop impl))] [(list op args ...) (cons op (map loop args))]))) (define (batch-replace-expression! batch from to) (define from* (deref (batch-add! batch from))) ;; a hack on how not to use deref for "from" (define (f node) (match node [(== from*) to] [(? number?) node] [(? literal?) node] [(? symbol?) node] [(approx spec impl) (approx spec impl)] [(list op args ...) (cons op args)])) (batch-recurse batch (λ (brf recurse) (define node (deref brf)) (define node* (f node)) (let loop ([node* node*]) (match node* [(? batchref? brf) (recurse brf)] [_ (batch-push! batch (expr-recurse node* (compose batchref-idx loop)))]))))) ;; Replace all occurrences of `from` with `to` in expression `expr`, returning a new batchref ;; Only recurses into impl parts, not specs (define (batch-replace-subexpr batch expr from to [can-refer #f]) (define cache (make-hasheq)) (define from-idx (batchref-idx from)) (let loop ([brf expr]) (define idx (batchref-idx brf)) (cond [(< idx from-idx) brf] [(= idx from-idx) to] [(and can-refer (not (set-member? can-refer idx))) brf] [else (hash-ref! cache idx (lambda () (match (deref brf) [(approx spec impl) (define impl* (loop impl)) (if (= (batchref-idx impl*) (batchref-idx impl)) brf (batch-push! batch (approx (batchref-idx spec) (batchref-idx impl*))))] [node (define unchanged? #t) (define node* (expr-recurse node (lambda (arg) (define arg* (loop arg)) (unless (= (batchref-idx arg*) (batchref-idx arg)) (set! unchanged? #f)) (batchref-idx arg*)))) (if unchanged? brf (batch-push! batch node*))])))]))) (module+ test (require rackunit) (check-equal? (replace-expression '(- x (sin x)) 'x 1) '(- 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))))))) ================================================ FILE: src/core/prove-rules.rkt ================================================ #lang racket (require rackunit) (require "../utils/common.rkt" "../syntax/matcher.rkt" "programs.rkt" "rules.rkt") (provide rewrite-unsound?) (define (undefined-conditions x) (reap [sow] (for ([subexpr (in-list (all-subexpressions x))]) (match subexpr [`(acos ,x) (sow `(< 1 (fabs ,x)))] [`(acosh ,x) (sow `(< ,x 1))] [`(asin ,x) (sow `(< 1 (fabs ,x)))] [`(atanh ,x) (sow `(<= 1 (fabs ,x)))] [`(fmod ,x ,y) (sow `(== ,y 0))] [`(lgamma ,x) (sow `(and (<= ,x 0) (integer? ,x)))] [`(log ,x) (sow `(<= ,x 0))] [`(log10 ,x) (sow `(<= ,x 0))] [`(log2 ,x) (sow `(<= ,x 0))] [`(logb ,x) (sow `(== ,x 0))] [`(remainder ,x ,y) (sow `(== ,y 0))] [`(sqrt ,x) (sow `(< ,x 0))] [`(tan ,x) (sow `(== (cos ,x) 0))] [`(tgamma ,x) (sow `(and (<= ,x 0) (integer? ,x)))] [`(pow ,a ,b) (sow `(and (< ,a 0) (even-denominator? ,b))) (sow `(and (== ,a 0) (< ,b 0)))] [`(/ ,a ,b) (sow `(== ,b 0))] [_ (void)])))) (define (reify c) (if c '(TRUE) '(FALSE))) ;; In general, the normal forms are: ;; - Only use ==, < conditions ;; - One side of a comparison is always a constant ;; - For ==, the constant is on the right (define (rewrite-all expr a b) ;; This is an ugly / slow way to do this but I guess it works (define matches (for/list ([subexpr (in-list (all-subexpressions expr))] #:when (pattern-match a subexpr)) (cons subexpr (pattern-substitute b (pattern-match a subexpr))))) (for/fold ([expr expr]) ([(from to) (in-dict matches)]) (replace-expression expr from to))) (define simplify-patterns (list '[(cos (neg a)) . (cos a)] '[(sin (neg a)) . (neg (sin a))] '[(cos (+ a (PI))) . (neg (cos a))] '[(cos (+ a (/ (PI) 2))) . (neg (sin a))] '[(cos (acos a)) . a] '[(cos (asin a)) . (sqrt (- 1 (* a a)))] '[(fabs (neg a)) . (fabs a)] '[(fabs (fabs a)) . (fabs a)] '[(pow x 2) . (* x x)])) (define (simplify-expression expr) (for/fold ([expr expr]) ([(a b) (in-dict simplify-patterns)]) (rewrite-all expr a b))) (define (simplify-condition term) (match term [`(== ,(? number? a) ,(? number? b)) (reify (= a b))] [`(< ,(? number? a) ,(? number? b)) (reify (< a b))] [`(> ,(? number? a) ,(? number? b)) (reify (> a b))] [`(== (PI) ,(? number?)) '(FALSE)] [`(== ,(? number? a) ,b) `(== ,b ,a)] ; canonicalize [`(== (+ ,(? number? a) ,b) ,c) `(== (+ ,b ,a) ,c)] ; canonicalize [`(== (- ,(? number? a) ,b) ,c) `(== (neg (- ,b ,a)) ,c)] ; canonicalize [`(<= ,a ,b) `(or (< ,a ,b) (== ,a ,b))] ; canonicalize [`(== (cbrt ,a) 0) `(== ,a 0)] [`(== (sqrt ,a) 0) `(== ,a 0)] [`(== (neg ,a) 0) `(== ,a 0)] [`(== (fabs ,a) 0) `(== ,a 0)] [`(== (* ,a ,b) 0) `(or (== ,a 0) (== ,b 0))] [`(== (/ ,a ,b) 0) `(== ,a 0)] [`(== (pow ,a ,b) 0) `(and (== ,a 0) (> ,b 0))] [`(== (fabs ,a) 1) `(or (== ,a 1) (== ,a -1))] [`(== (+ ,x 1) 0) `(== ,x -1)] [`(== (- ,a 1) 0) `(== ,a 1)] [`(== (* ,a ,a) 1) `(== (fabs ,a) 1)] [`(< (* ,a ,a) 0) '(FALSE)] [`(< (sqrt ,a) 0) '(FALSE)] [`(< (fabs ,a) 0) '(FALSE)] [`(,(or '< '==) (cosh ,a) ,(? (conjoin number? (curryr < 1)))) '(FALSE)] [`(,(or '< '==) (exp ,a) ,(? (conjoin number? (curryr <= 0)))) '(FALSE)] [`(,(or '< '==) (* ,a ,a) ,(? (conjoin number? (curryr < 0)))) '(FALSE)] [`(,(or '< '==) (fabs ,a) ,(? (conjoin number? (curryr < 0)))) '(FALSE)] [`(< (/ 1 ,a) 0) `(< ,a 0)] [`(> (/ 1 ,a) 0) `(> ,a 0)] [`(< (/ -1 ,a) 0) `(> ,a 0)] [`(< (neg ,a) 0) `(> ,a 0)] [`(> (neg ,a) 0) `(< ,a 0)] [`(< (* 2 ,a) ,(? number? b)) `(< ,a ,(/ b 2))] [`(< (+ 1 ,a) 0) `(< ,a -1)] [`(< (/ ,x ,(? (conjoin number? positive?))) 0) `(< ,x 0)] [`(< (+ ,x 1) 0) `(< ,x -1)] [`(< (- ,a 1) ,(? number? b)) `(< ,a ,(+ b 1))] [`(< (- 1 ,x) 0) `(< 1 ,x)] [`(< (cbrt ,a) 0) `(< ,a 0)] [`(< (* ,a ,a) 1) `(< (fabs ,a) 1)] [`(< 1 (* ,a ,a)) `(< 1 (fabs ,a))] [`(== (+ ,x (sqrt (+ (* ,x ,x) 1))) 0) '(FALSE)] [`(== (+ ,x (sqrt (- (* ,x ,x) 1))) 0) '(FALSE)] [`(< (+ ,x (sqrt (+ (* ,x ,x) 1))) 0) '(FALSE)] [`(< (+ ,x (sqrt (- (* ,x ,x) 1))) 0) `(<= x -1)] [`(< 1 (fabs (,(or 'cos 'sin) x))) '(FALSE)] [`(== (/ (+ 1 ,x) (- 1 ,x)) 0) `(== ,x -1)] [`(< (/ (+ 1 ,x) (- 1 ,x)) 0) `(< 1 (fabs x))] [`(== (+ (cos ,a) (cos ,b)) 0) `(or (== (cos (/ (+ ,a ,b) 2)) 0) (== (cos (/ (- ,a ,b) 2)) 0))] [`(== (cos (* 2 ,a)) 0) `(or (== (tan ,a) 1) (== (tan ,a) -1))] [`(== (tan ,a) 0) `(== (sin ,a) 0)] [`(even-denominator? (neg ,b)) `(even-denominator? ,b)] [`(even-denominator? (+ ,b 1)) `(even-denominator? ,b)] [`(even-denominator? (/ ,b 3)) `(even-denominator? ,b)] [`(even-denominator? ,(? rational? a)) (if (even? (denominator a)) '(TRUE) '(FALSE))] [`(and ,sub ...) (define subs (map (compose simplify-conditions list) sub)) (define conjunctions (apply cartesian-product subs)) (cons 'or (for/list ([conj (in-list conjunctions)]) (match (set-remove conj '(TRUE)) ['(FALSE) '(TRUE)] [(list a) a] [(list as ...) (cons 'and as)])))] [_ term])) (define (simplify-conditions xs) (define simple1 (reap [sow] (for ([x (remove-duplicates xs)]) (define out (simplify-condition (simplify-expression x))) (match out [`(or ,terms ...) (for-each sow terms)] [`(FALSE) (void)] [_ (sow out)])))) (if (equal? simple1 xs) xs (simplify-conditions simple1))) ;; The prover must prove: rhs-bad => lhs-bad ;; IOW we can weaken the RHS or strengthen the LHS (define soundness-proofs '((pow-plus (implies (< b -1) (< b 0))) (pow-sqr (implies (even-denominator? (* 2 b)) (even-denominator? b))) (hang-0p-tan (implies (== (cos (/ a 2)) 0) (== (cos a) -1))) (hang-0p-tan-rev (implies (== (cos a) -1) (== (cos (/ a 2)) 0))) (hang-0m-tan (implies (== (cos (/ (neg a) 2)) 0) (== (cos a) -1))) (hang-0m-tan-rev (implies (== (cos a) -1) (== (cos (/ (neg a) 2)) 0))) (tanh-sum (implies (== (* (tanh x) (tanh y)) -1) (FALSE))) (tanh-def-a (implies (== (+ (exp x) (exp (neg x))) 0) (FALSE))) (acosh-def (implies (< x -1) (< x 1)) (implies (== x -1) (< x 1)) (implies (< (fabs x) 1) (< x 1))) (acosh-def-rev (implies (< x 1) (or (< x -1) (== x -1) (< (fabs x) 1)))) (sqrt-undiv (implies (< (/ x y) 0) (or (< x 0) (< y 0)))) (sqrt-unprod (implies (< (* x y) 0) (or (< x 0) (< y 0)))) (sqrt-pow2 (implies (and (< x 0) _) (< x 0))) (tan-sum-rev (implies (== (cos (+ x y)) 0) (== (* (tan x) (tan y)) 1))) (sum-log (implies (< (* x y) 0) (or (< x 0) (< y 0)))) (diff-log (implies (< (/ x y) 0) (or (< x 0) (< y 0)))) (exp-to-pow (implies (and a b) a)) (acosh-2-rev (implies (< (fabs x) 1) (< x 1))) (tanh-acosh (implies (< (fabs x) 1) (< x 1)) (implies (== x 0) (< x 1))) (sinh-acosh (implies (< (fabs x) 1) (< x 1))) (hang-p0-tan (implies (== (cos (/ a 2)) 0) (== (sin a) 0))) (hang-m0-tan (implies (== (cos (/ a 2)) 0) (== (sin a) 0))) (pow-div (implies (< (- b c) 0) (or (< b 0) (> c 0))) (implies (even-denominator? (- b c)) (or (even-denominator? b) (even-denominator? c)))) (pow-prod-up (implies (< (+ b c) 0) (or (< b 0) (< c 0))) (implies (even-denominator? (+ b c)) (or (even-denominator? b) (even-denominator? c)))) (pow-prod-down (implies (< (* b c) 0) (or (< b 0) (< c 0)))) (log-pow-rev (implies (and a b) a) (implies (< (pow a b) 0) (< a 0))))) (define (execute-proof proof terms) (for/fold ([terms (simplify-conditions terms)]) ([step (in-list proof)]) (match-define `(implies ,a ,b) step) (simplify-conditions (map (curryr rewrite-all a b) terms)))) (define (rewrite-unsound? lhs rhs [proof '()]) (define lhs-bad (simplify-conditions (undefined-conditions lhs))) (define rhs-bad (execute-proof proof (undefined-conditions rhs))) (define extra (set-remove (set-subtract rhs-bad lhs-bad) '(FALSE))) (if (empty? extra) (values #f #f) (values lhs-bad extra))) (define (potentially-unsound) (for ([rule (in-list (*rules*))]) (test-case (~a (rule-name rule)) (define proof (dict-ref soundness-proofs (rule-name rule) '())) (define-values (lhs-bad rhs-bad) (rewrite-unsound? (rule-input rule) (rule-output rule) proof)) (when rhs-bad (with-check-info (['lhs-bad lhs-bad]) (check-false rhs-bad)))))) (module+ test (potentially-unsound)) ================================================ FILE: src/core/regimes.rkt ================================================ #lang racket ;;;; Module principles ;; - The core of this file is infer-option-prefixes. ;; It is a giant dynamic programming algorithm. ;; It is extremely performance-sensitive. ;; - Therefore almost everything is vector-based with few copies. ;; Except exprs-to-branch-on. Converting it to vectors makes it slow. ;; - Everything else is overhead and should be minimized. (require math/flonum "../core/alternative.rkt" "../utils/common.rkt" "../utils/pareto.rkt" "../syntax/float.rkt" "../utils/timeline.rkt" "../syntax/types.rkt" "../syntax/batch.rkt" "compiler.rkt" "points.rkt" "programs.rkt") (provide pareto-regimes (struct-out option) (struct-out si) critical-subexpression?) (module+ test (require rackunit "../syntax/syntax.rkt" "../syntax/sugar.rkt")) (struct option (split-indices alts pts expr) #:transparent #:methods gen:custom-write [(define (write-proc opt port mode) (fprintf port "#
" out)
    ((error-display-handler) (exn-message e) e)
    (display "
" out))) (define (make-page-timeout page out result-hash output? profile? #:timeout [timeout +inf.0]) (define e (engine (lambda (_) (make-page page out result-hash output? profile?)))) (if (engine-run timeout e) (engine-result e) (display "

Timeout generating page

" out))) (define (make-page page out result-hash output? profile?) (match page ["graph.html" (write-html (make-graph-html result-hash output? profile?) out)] ["timeline.html" (define name (hash-ref result-hash 'name)) (write-html (make-timeline name (hash-ref result-hash 'timeline) #:path "..") out)] ["timeline.json" (write-json (hash-ref result-hash 'timeline) out)] ["profile.json" (write-json (hash-ref result-hash 'profile) out)] ["points.json" (write-json (with-handlers ([exn:fail? (lambda _ '())]) (make-points-json result-hash)) out)])) (define (make-graph-html result-hash output? profile?) (define status (hash-ref result-hash 'status)) (match status ["success" (define command (hash-ref result-hash 'command)) (match command ["improve" (make-graph result-hash output? profile?)] [else (dummy-graph command)])] ["timeout" (make-traceback result-hash)] ["failure" (make-traceback result-hash)])) ================================================ FILE: src/reports/plot.rkt ================================================ #lang racket (require math/bigfloat math/flonum) (require "../core/points.rkt" "../syntax/float.rkt" "../core/programs.rkt" "../syntax/types.rkt" "../syntax/read.rkt" "../core/alternative.rkt" "../core/bsearch.rkt") (provide make-points-json regime-var regime-splitpoints real->ordinal splitpoints->json) (define (bits->tenths x) (string->number (real->decimal-string x 1))) (define (splitpoints->json vars alt repr) (for/list ([var (in-list vars)]) (define split-var? (equal? var (regime-var alt))) (if split-var? (for/list ([val (regime-splitpoints alt)]) (real->ordinal (repr->real val repr) repr)) '()))) (define (make-points-json result-hash) (define test (car (load-tests (open-input-string (hash-ref result-hash 'test))))) (define backend (hash-ref result-hash 'backend)) (define test-points (map first (hash-ref backend 'pcontext))) (define repr (test-output-repr test)) (define start-errors (hash-ref (hash-ref backend 'start) 'errors)) (define target-errors (map (curryr hash-ref 'errors) (hash-ref backend 'target))) (define end-errors (map (curryr hash-ref 'errors) (hash-ref backend 'end))) ; Immediately convert points to reals to handle posits (define ordinal-points (for/list ([point (in-list test-points)]) (for/list ([x (in-list point)] [repr (in-list (test-var-reprs test))]) ((representation-repr->ordinal repr) (json->value x repr))))) (define-values (mins maxs) (for/fold ([mins #f] [maxs #f]) ([point (in-list ordinal-points)]) (values (if mins (map min point mins) point) (if maxs (map max point maxs) point)))) (define vars (test-vars test)) (define bits (representation-total-bits repr)) (define start-error (for/list ([err (in-list start-errors)]) (bits->tenths err))) (define target-error (for/list ([alt-error (in-list target-errors)]) (for/list ([err (in-list alt-error)]) (bits->tenths err)))) (define end-error (for/list ([err (in-list (car end-errors))]) (bits->tenths err))) (define target-error-entries (for/list ([i (in-naturals)] [error-value (in-list target-error)]) (cons (format "target~a" (+ i 1)) error-value))) (define error-entries (list* (cons "start" start-error) (cons "end" end-error) target-error-entries)) (define ticks (for/list ([var (in-list vars)] [idx (in-naturals)] [min-ordinal (in-list mins)] [max-ordinal (in-list maxs)] [repr (in-list (test-var-reprs test))] ; We want to bail out since choose-ticks will crash otherwise #:unless (equal? min-ordinal max-ordinal)) (define min-val (repr->real ((representation-ordinal->repr repr) min-ordinal) repr)) (define max-val (repr->real ((representation-ordinal->repr repr) max-ordinal) repr)) (define real-ticks (choose-ticks min-val max-val repr)) (for/list ([val (in-list real-ticks)]) (define tick-str (if (or (= val 0) (< 0.01 (abs val) 100)) (~r (exact->inexact val) #:precision 4) (string-replace (~r val #:notation 'exponential #:precision 0) "1e" "e"))) (list tick-str (real->ordinal val repr))))) (define splitpoints (hash-ref (car (hash-ref backend 'end)) 'splitpoints)) ; NOTE ordinals currently remain numeric; if we need to detect truncation ; in JSON, we can switch to string encoding. ; Fields: ; bits: int representing the maximum possible bits of error ; vars: array of n string variable names ; points: array of size m like [[x0, x1, ..., xn], ...] where x0 etc. ; are ordinals representing the real input values ; error: JSON dictionary where keys are {start, end, target1, ..., targetn}. ; Each key's value holds an array like [y0, ..., ym] where y0 etc are ; bits of error for the output on each point ; ticks_by_varidx: array of size n where each entry is [label, ordinal] pairs ; splitpoints_by_varidx: array of size n where each entry has ordinal splitpoints `#hasheq((bits . ,bits) (vars . ,(map symbol->string vars)) (points . ,ordinal-points) (error . ,error-entries) (ticks_by_varidx . ,ticks) (splitpoints_by_varidx . ,splitpoints))) ;; Repr conversions (define (ordinal->real x repr) (repr->real ((representation-ordinal->repr repr) x) repr)) (define (real->ordinal x repr) ((representation-repr->ordinal repr) (real->repr x repr))) (define (first-power10 min max repr) (define value (cond [(negative? max) (- (expt 10 (ceiling (/ (log (- max)) (log 10)))))] [else (expt 10 (floor (/ (log max) (log 10))))])) (if (<= value min) #f value)) (define (clamp x lo hi) (min hi (max x lo))) (define (choose-between min max number repr) ; Returns a given number of ticks, roughly evenly spaced, between min and max ; For any tick, n divisions below max, the tick is an ordinal corresponding to: ; (a) a power of 10 between n and (n + ε) divisions below max where ε is some tolerance, or ; (b) a value, n divisions below max (define sub-range (round (/ (- max min) (add1 number)))) (define (near x n) (and (<= x n) (<= (abs (/ (- x n) sub-range)) 0.2))) ; <- tolerance (for/list ([itr (in-range 1 (add1 number))]) (define power10 (first-power10 (ordinal->real (clamp (- max (* (add1 itr) sub-range)) min max) repr) (ordinal->real (clamp (- max (* itr sub-range)) min max) repr) repr)) (if (and power10 (near (real->ordinal power10 repr) (- max (* itr sub-range)))) (real->ordinal power10 repr) (- max (* itr sub-range))))) (define (pick-spaced-ordinals necessary min max number repr) (define sub-range (/ (- max min) number)) ; size of a division on the ordinal range (define necessary* ; filter out necessary points that are too close (let loop ([necessary necessary]) (cond [(< (length necessary) 2) necessary] [(< (- (cadr necessary) (car necessary)) sub-range) (loop (cdr necessary))] [else (cons (car necessary) (loop (cdr necessary)))]))) (define all (let loop ([necessary necessary*] [min* min] [start 0]) (cond [(>= start number) '()] [(empty? necessary) (choose-between min* max (- number start) repr)] [else (define idx (for/first ([i (in-range number)] #:when (<= (- (first necessary) (+ min (* i sub-range))) sub-range)) i)) (append (choose-between min* (first necessary) (- idx start) repr) (loop (cdr necessary) (first necessary) (add1 idx)))]))) (sort (append all necessary*) <)) (define (choose-ticks min max repr) (define tick-count 13) (define necessary (map (curryr real->ordinal repr) (filter (λ (x) (<= min x max)) (list min -1.0 0.0 1.0 max)))) (map (curryr ordinal->real repr) (pick-spaced-ordinals necessary (real->ordinal min repr) (real->ordinal max repr) tick-count repr))) (define/contract (regime-info altn) (-> alt? (or/c (listof sp?) #f)) (let loop ([altn altn]) (match altn [(alt _ `(regimes ,splitpoints) prevs) splitpoints] [(alt _ _ (list)) #f] [(alt _ _ (list prev _ ...)) (loop prev)]))) (define (regime-splitpoints altn) (map sp-point (drop-right (regime-info altn) 1))) (define/contract (regime-var altn) (-> alt? (or/c expr? #f)) (define info (regime-info altn)) (and info (sp-bexpr (car info)))) ================================================ FILE: src/reports/resources/404.html ================================================ Herbie Page Not Found

Page Not Found

You're looking for a page that does not exist. Turn back!

================================================ FILE: src/reports/resources/demo.js ================================================ CONSTANTS = {"PI": "real", "E": "real", "TRUE": "bool", "FALSE": "bool"} FUNCTIONS = {} "+ - * / pow copysign fdim fmin fmax fmod hypot remainder atan2".split(" ").forEach(function(op) { FUNCTIONS[op] = [["real", "real"], "real"]; }); ("fabs sqrt exp log sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh " + "cbrt ceil erf erfc exp2 expm1 floor lgamma log10 log1p log2 logb rint " + "round tgamma trunc").split(" ").forEach(function(op) { FUNCTIONS[op] = [["real"], "real"]; }); FUNCTIONS["fma"] = [["real", "real", "real"], "real"]; "< > == != <= >=".split(" ").forEach(function(op) { FUNCTIONS[op] = [["real", "real"], "bool"]; }); "and or".split(" ").forEach(function(op) { FUNCTIONS[op] = [["bool", "bool"], "bool"]; }); SECRETFUNCTIONS = {"^": "pow", "**": "pow", "abs": "fabs", "min": "fmin", "max": "fmax", "mod": "fmod"} function tree_errors(tree, expected) /* tree -> list */ { var messages = []; var names = []; var rtype = bottom_up(tree, function(node, path, parent) { switch(node.type) { case "ConstantNode": if (["number", "boolean"].indexOf(node.valueType) === -1) { messages.push("Constants that are " + node.valueType + "s not supported."); } return ({"number": "real", "boolean": "bool"})[node.valueType] || "real"; case "FunctionNode": node.name = SECRETFUNCTIONS[node.name] || node.name; if (!FUNCTIONS[node.name]) { messages.push("Function " + node.name + " unsupported."); } else if (FUNCTIONS[node.name][0].length !== node.args.length) { messages.push("Function " + node.name + " expects " + FUNCTIONS[node.name][0].length + " arguments"); } else if (""+extract(node.args) !== ""+FUNCTIONS[node.name][0]) { messages.push("Function " + node.name + "" + " expects arguments of type " + FUNCTIONS[node.name][0].join(", ") + ", got " + extract(node.args).join(", ")); } return (FUNCTIONS[node.name] || [[], "real"])[1]; case "OperatorNode": node.op = SECRETFUNCTIONS[node.op] || node.op; if (!FUNCTIONS[node.op]) { messages.push("Operator " + node.op + " unsupported."); } else if (FUNCTIONS[node.op][0].length !== node.args.length && !(node.op === "-" && node.args.length === 1)) { messages.push("Operator " + node.op + " expects " + FUNCTIONS[node.op][0].length + " arguments"); } else if (""+extract(node.args) !== ""+FUNCTIONS[node.op][0] && !(node.op === "-" && ""+extract(node.args) === "real") && !(is_comparison(node.op) /* TODO improve */)) { messages.push("Operator " + node.op + "" + " expects arguments of type " + FUNCTIONS[node.op][0].join(", ") + ", got " + extract(node.args).join(", ")); } return (FUNCTIONS[node.op] || [[], "real"])[1]; case "SymbolNode": if (!CONSTANTS[node.name]) { names.push(node.name); return "real"; } else { return CONSTANTS[node.name]; } case "ConditionalNode": if (node.condition.res !== "bool") { messages.push("Conditional has type " + node.condition.res + " instead of bool"); } if (node.trueExpr.res !== node.falseExpr.res) { messages.push("Conditional branches have different types " + node.trueExpr.res + " and " + node.falseExpr.res); } return node.trueExpr.res; case "BlockNode": for (var i = 0; i < tree.blocks.length - 1; i++) { stmt = tree.blocks[i].node; if (stmt.type != "AssignmentNode") messages.push("Expected an assignment statement before a semicolon: " + stmt); } return node.blocks[node.blocks.length - 1].node.res; default: messages.push("Unsupported syntax; found unexpected " + node.type + ".") return "real"; } }).res; if (rtype !== expected) { messages.push("Expected an expression of type " + expected + ", got " + rtype); } return messages; } function bottom_up(tree, cb) { if (tree.args) { tree.args = tree.args.map(function(node) {return bottom_up(node, cb)}); } else if (tree.condition) { tree.condition = bottom_up(tree.condition, cb); tree.trueExpr = bottom_up(tree.trueExpr, cb); tree.falseExpr = bottom_up(tree.falseExpr, cb); } else if (tree.blocks) { for (var i = 0; i < tree.blocks.length - 1; i++) { stmt = tree.blocks[i].node; if (stmt.type != "AssignmentNode") throw SyntaxError("Expected an assignment statement before a semicolon: " + stmt); stmt.expr = bottom_up(stmt.expr, cb); } tree.blocks[tree.blocks.length - 1].node = bottom_up(tree.blocks[tree.blocks.length - 1].node, cb); } tree.res = cb(tree); return tree; } function extract(args) { return args.map(function(n) { return n.res }); } function dump_fpcore(formula) { var tree = math.parse(formula); var names = []; var body = dump_tree(tree, names); var precondition = get_precondition_from_input_ranges(formula); var dnames = []; for (var i = 0; i < names.length; i++) { if (dnames.indexOf(names[i]) === -1) dnames.push(names[i]); } var name = formula.replace("\\", "\\\\").replace("\"", "\\\""); var fpcore = "(FPCore (" + dnames.join(" ") + ")\n :name \"" + name + "\""; if (precondition) fpcore += "\n :pre " + precondition; return fpcore + "\n " + body + ")"; } function is_comparison(name) { return ["==", "!=", "<", ">", "<=", ">="].indexOf(name) !== -1; } function flatten_comparisons(node) { var terms = []; (function collect_terms(node) { if (node.type == "OperatorNode" && is_comparison(node.op)) { collect_terms(node.args[0]); collect_terms(node.args[1]); } else { terms.push(node.res); } })(node); var conjuncts = []; var iters = 0; (function do_flatten(node) { if (node.type == "OperatorNode" && is_comparison(node.op)) { do_flatten(node.args[0]); var i = iters++; // save old value and increment it var prev = conjuncts[conjuncts.length - 1]; if (prev && prev[0] == node.op && prev[2] == terms[i]) { prev.push(terms[i + 1]); } else { conjuncts.push([node.op, terms[i], terms[i+1]]); } do_flatten(node.args[1]); } })(node); var comparisons = []; for (var i = 0; i < conjuncts.length; i++) { comparisons.push("(" + conjuncts[i].join(" ") + ")"); } if (comparisons.length == 0) { return "TRUE"; } else if (comparisons.length == 1) { return comparisons[0]; } else { return "(and " + comparisons.join(" ") + ")"; } } function dump_tree(tree, names) { function rec(node, bound) { switch(node.type) { case "ConstantNode": node.res = "" + node.value; return node; case "FunctionNode": node.args = node.args.map(function(n) { return rec(n, bound) }); node.name = SECRETFUNCTIONS[node.name] || node.name; node.res = "(" + node.name + " " + extract(node.args).join(" ") + ")"; return node; case "OperatorNode": node.args = node.args.map(function(n) { return rec(n, bound) }); node.op = SECRETFUNCTIONS[node.op] || node.op; if (is_comparison(node.op)) { node.res = flatten_comparisons(node); } else { node.res = "(" + node.op + " " + extract(node.args).join(" ") + ")"; } return node; case "SymbolNode": if (!CONSTANTS[node.name] && bound.indexOf(node.name) == -1) names.push(node.name); node.res = node.name; return node; case "ConditionalNode": node.condition = rec(node.condition, bound); node.trueExpr = rec(node.trueExpr, bound); node.falseExpr = rec(node.falseExpr, bound); node.res = "(if " + node.condition.res + " " + node.trueExpr.res + " " + node.falseExpr.res + ")"; return node; case "BlockNode": str = ""; for (var i = 0; i < node.blocks.length - 1; i++) { stmt = node.blocks[i].node; if (stmt.type != "AssignmentNode") throw SyntaxError("Expected an assignment statement before a semicolon: " + stmt); rec(stmt.expr, bound); str += ("(let ((" + stmt.name + " " + stmt.expr.res + ")) "); if (bound.indexOf(stmt.name) == -1) bound.push(stmt.name); } rec(node.blocks[node.blocks.length - 1].node, bound); node.res = str + node.blocks[node.blocks.length - 1].node.res + ")".repeat(node.blocks.length - 1); return node; default: throw SyntaxError("Invalid tree! " + node); } } rec(tree, []); return tree.res; } function get_unused_var_warnings(tree) { let unused = []; bottom_up(tree, function(node) { switch(node.type) { case "ConstantNode": return new Set(); case "FunctionNode": case "OperatorNode": used = new Set(); extract(node.args).forEach(function(s) { s.forEach(function(n) { used.add(n); }); }) return used; case "SymbolNode": if (!CONSTANTS[node.name]) return new Set([node.name]); else return new Set(); case "ConditionalNode": usedCond = node.condition.res; usedTrue = node.trueExpr.res; usedFalse = node.falseExpr.res; return new Set([...usedCond, ...usedTrue, ...usedFalse]) case "BlockNode": bound = []; usedInAssigns = []; for (var i = 0; i < node.blocks.length - 1; i++) { stmt = node.blocks[i].node; if (stmt.type != "AssignmentNode") throw SyntaxError("Expected an assignment statement before a semicolon: " + stmt); bound.push(stmt.name); usedInAssigns.push(stmt.expr.res); } // Assume each assignment is of the form: // ::= = ; . // Then // (i) is unused if is not in Used(), // (ii) Used() = Used() U (Used() \ { }) // Clearly, the assumption is slightly wrong, but this // tells us we just walk backwards checking condition (i) // and updating the used set with (ii). used = node.blocks[node.blocks.length - 1].node.res; for (var i = node.blocks.length - 2; i >= 0; i--) { if (!used.has(bound[i])) unused.push("UnboundVariable: " + bound[i]); used.delete(bound[i]); used = new Set([...used, ...usedInAssigns[i]]); } return used default: throw SyntaxError("Invalid tree!"); } }); return unused; } function get_errors() { var tree, errors = []; let parse_fail = false for (var i = 0; i < arguments.length; i++) { try { tree = math.parse(arguments[i][0]); errors = errors.concat(tree_errors(tree, arguments[i][1])); } catch (e) { parse_fail = true errors.push("" + e); } } if (!parse_fail) { const input = document.querySelector("[name=formula-math]") errors = [...errors, ...get_varnames_mathjs(input.value).map(varname => get_input_range_errors(KNOWN_INPUT_RANGES[varname], true).map(s => "RangeError: " + varname + ": " + s)).flat()] } return errors; } function get_warnings() { try { const input = document.querySelector("[name=formula-math]") rangeWarnings = get_varnames_mathjs(input.value).map(varname => get_input_range_warnings(KNOWN_INPUT_RANGES[varname]).map(s => "RangeWarning: " + varname + ": " + s)).flat(); unusedWarnings = get_unused_var_warnings(math.parse(input.value)) return rangeWarnings.concat(unusedWarnings); } catch (e) { return [] } } function check_errors() { var input = document.querySelector("[name=formula-math]"); var errors = get_errors([input.value, "real"]); var warnings = get_warnings(); document.getElementById("errors").innerHTML = errors.length == 0 || !input.value ? "" : "
  • " + errors.join("
  • ") + "
  • "; document.getElementById("warnings").innerHTML = warnings.length == 0 || !input.value ? "" : "
  • " + warnings.join("
  • ") + "
  • "; if (!input.value) { return false; } else if (errors.length > 0) { return false; } else { return true; } } function hide_extra_fields() { var $extra = document.querySelector("#formula .extra-fields"); var inputs = $extra.querySelectorAll("input, select"); for (var i = 0; i < inputs.length; i++) { if (inputs[i].tagName == "INPUT" && inputs[i].value) return; if (inputs[i].tagName == "SELECT" && inputs[i].selectedIndex) return; } var $a = document.createElement("a"); $a.textContent = "Advanced options »"; $a.classList.add("show-extra"); $extra.parentNode.insertBefore($a, $extra.nextSibling); $a.addEventListener("click", function() { $extra.style.display = "block"; $a.style.display = "none"; }); } var STATE = null; function Form(form) { this.form = form; this.fpcore = form.querySelector("[name=formula]"); this.math = form.querySelector("[name=formula-math]"); this.extra_fields = form.querySelector(".extra-fields"); this.math_doc = document.getElementById("mathjs-instructions"); this.lisp_doc = document.getElementById("lisp-instructions"); this.input_ranges = document.getElementById("input-ranges"); this.extra_links = form.querySelector(".extra-links"); this.button = form.querySelector("[type=submit]") } function get_precondition_from_input_ranges(formula) { const checks = get_varnames_mathjs(formula) .map(name => ([name, KNOWN_INPUT_RANGES[name]])) .filter(([_, range]) => get_input_range_errors(range).length == 0) .map(([name, [start, end]]) => `(<= ${start} ${name} ${end})`) .join(' ') return checks.length == 0 ? "" : `(and ${checks})` } function setup_state(state, form) { window.STATE = state; form.fpcore.removeAttribute("disabled"); form.math.removeAttribute("disabled"); document.querySelector('#use-fpcore').onclick = function(evt) { if (form.math.value) { if (!check_errors()) { alert("Please fix all errors before attempting to use FPCore input.") return evt.preventDefault(); } var fpcore = dump_fpcore(form.math.value) form.fpcore.value = fpcore; } setup_state("fpcore", form); } document.querySelector('#show-example').onclick = function (evt) { form.math.value = "sqrt(x + 1) - sqrt(x)" CHECK_ERRORS_AND_DRAW_RANGES() document.querySelector('#x_low').value = "0"; document.querySelector('#x_high').value = "1.79e308"; window.KNOWN_INPUT_RANGES['x'] = [0, 1.79e308] update_run_button_mathjs(form) } if (state == "math") { form.fpcore.style.display = "none"; form.math.style.display = "block"; form.input_ranges.style.display = "table"; document.querySelector('#options').style.display = 'block'; document.querySelector("#lisp-instructions").style.display = "none"; document.querySelector("#mathjs-instructions").style.display = "block"; update_run_button_mathjs(form) } else { document.querySelector('#options').style.display = 'none'; form.fpcore.style.display = "block"; form.math.style.display = "none"; form.input_ranges.style.display = "none"; document.querySelector("#lisp-instructions").style.display = "block"; document.querySelector("#mathjs-instructions").style.display = "none"; form.button.classList.remove("hidden"); form.button.removeAttribute("disabled"); } } function get_varnames_mathjs(mathjs_text) { const names = [] dump_tree(math.parse(mathjs_text), names) var dnames = []; for (var i = 0; i < names.length; i++) { if (dnames.indexOf(names[i]) === -1) dnames.push(names[i]); } return dnames } function update_run_button_mathjs(form) { function no_range_errors([low, high] = [undefined, undefined]) { return low !== '' && high !== '' && !isNaN(Number(low)) && !isNaN(Number(high)) && Number(low) <= Number(high) } const button = document.querySelector('#run_herbie') let varnames; try { varnames = get_varnames_mathjs(form.math.value) } catch (e) { console.log("Couldn't get varnames:", e) button.setAttribute('disabled', 'true') return; } if (form.math.value && varnames.every(varname => no_range_errors(KNOWN_INPUT_RANGES[varname]))) { button.removeAttribute('disabled') } else { console.log('There are still range errors.', ) button.setAttribute('disabled', 'true') } } function get_input_range_errors([low, high] = [undefined, undefined], empty_if_missing=false) { if ((low === undefined || low === '') || (high === undefined || high === '')) return empty_if_missing ? [] : ['input missing'] const A = [] if (!(low === undefined || low === '') && isNaN(Number(low))) { A.push(`The start of the range (${low}) is not a number.`) } else if (!Number.isFinite(Number(low))) { A.push(`The start of the range (${low}) is outside the floating point range.`) } if (!(high === undefined || high === '') && isNaN(Number(high))) { A.push(`The end of the range (${high}) is not a number.`) } else if (!Number.isFinite(Number(high))) { A.push(`The end of the range (${high}) is outside the floating point range.`) } if (Number(low) > Number(high)) A.push(`The start of the range is higher than the end.`) return A } function get_input_range_warnings([low, high] = [undefined, undefined]) { if ((low === undefined || low === '') || (high === undefined || high === '')) return [] const A = [] if (Number(low) == Number(high)) A.push(`This is a single point.`) return A } function onload() { // Only records ranges the user intentionally set. window.KNOWN_INPUT_RANGES = { /* "x" : [-1, 1] */ } function hide(selector) { document.querySelector(selector).style.display = 'none' } hide('#formula textarea') var form = new Form(document.getElementById("formula")); /* STATE represents whether we are working with the mathjs + precondition inputs or the fpcore input. */ const params = new URLSearchParams(window.location.search); if (params.get('fpcore')) { form.fpcore.value = params.get('fpcore'); STATE = "fpcore"; } else if (form.math.value) STATE = "math"; else if (form.fpcore.value) STATE = "fpcore"; else STATE = "math"; setup_state(STATE, form); function html(string) { const t = document.createElement('template'); t.innerHTML = string; return t.content; } function range_inputs(varname) { const [low, high] = KNOWN_INPUT_RANGES[varname] || [undefined, undefined] const low_id = `${varname}_low` const high_id = `${varname}_high` const default_options = ['-1.79e308', '-1e9', '-1e3', '-1', '0', '1', '1e3', '1e9', '1.79e308'] function input_view(id, value, placeholder) { return ` ` } const error_msgs = get_input_range_errors(KNOWN_INPUT_RANGES[varname], true) const warning_msgs = get_warnings(KNOWN_INPUT_RANGES[varname]) const view = html(` ${varname}: ${input_view(low_id, low, '-1e3')} to ${input_view(high_id, high, '1e3')} `) const low_el = view.querySelector(`#${low_id}`) function show_errors(root=document) { root.querySelectorAll(`#${varname}_input input`).forEach(input => { input.style['background-color'] = input.value === '' ? '#a6ffff3d' : 'initial' }) } function set_input_range({ low, high }) { if (low !== undefined) low_el.value = low if (high !== undefined) high_el.value = high if (!KNOWN_INPUT_RANGES[varname]) { KNOWN_INPUT_RANGES[varname] = [undefined, undefined] } const [old_low, old_high] = KNOWN_INPUT_RANGES[varname] KNOWN_INPUT_RANGES[varname] = [low ?? old_low, high ?? old_high] check_errors() show_errors() update_run_button_mathjs(form) } low_el.addEventListener('input', () => set_input_range({ low: low_el.value })) view.querySelectorAll(`#${low_id}_dropdown .dropdown-content div`).forEach((e, i) => { const handler = () => set_input_range({ low: e.textContent }) e.addEventListener('click', handler) e.addEventListener('pointerdown', handler) }) const high_el = view.querySelector(`#${high_id}`) high_el.addEventListener('input', () => set_input_range({ high: high_el.value })) view.querySelectorAll(`#${high_id}_dropdown .dropdown-content div`).forEach((e, i) => { const handler = () => set_input_range({ high: e.textContent }) e.addEventListener('click', handler) e.addEventListener('pointerdown', handler) }) check_errors() show_errors(view) return view } const range_div = document.createElement('DIV') document.querySelector('#formula').appendChild(range_div) update_run_button_mathjs(form) let current_timeout = undefined // can be used to debounce the input box function check_errors_and_draw_ranges() { if (form.math.value === "") document.querySelector('#input-ranges').innerHTML='' let varnames; try { varnames = get_varnames_mathjs(form.math.value) } catch (e) { check_errors() return } const range_div = document.querySelector('#input-ranges') range_div.replaceChildren(...varnames.map(range_inputs)) } // Expose the updater so generated range callbacks can trigger a refresh. CHECK_ERRORS_AND_DRAW_RANGES = check_errors_and_draw_ranges check_errors_and_draw_ranges() form.math.addEventListener("input", function () { clearTimeout(current_timeout) current_timeout = setTimeout(check_errors_and_draw_ranges, 400) update_run_button_mathjs(form) }) form.math.setAttribute('autocomplete', 'off') // (because it hides the error output) form.form.addEventListener("submit", function(evt) { var fpcore; if (STATE != "fpcore") { if (!check_errors()) return evt.preventDefault(); fpcore = dump_fpcore(form.math.value) } else { fpcore = form.fpcore.value; } console.log(STATE, fpcore); var url = document.getElementById("formula").getAttribute("data-progress"); if (url) { form.math.disabled = "true"; form.fpcore.disabled = "true"; ajax_submit(url, fpcore); evt.preventDefault(); return false; } else { return true; } }); } function get_progress(loc) { var req2 = new XMLHttpRequest(); req2.open("GET", loc); req2.onreadystatechange = function() { if (req2.readyState == 4) { if (req2.status == 202) { document.getElementById("progress").textContent = req2.responseText; const nums = document.getElementById("num-jobs"); if (nums != null) { nums.textContent = req2.getResponseHeader("X-Job-Count"); } setTimeout(function() {get_progress(loc)}, 100); } else if (req2.status == 201) { var loc2 = req2.getResponseHeader("Location"); var form = new Form(document.getElementById("formula")); form.math.removeAttribute("disabled"); form.fpcore.removeAttribute("disabled"); form.button.removeAttribute("disabled"); window.location.href = loc2; } else { document.getElementById("errors").innerHTML = req2.responseText; } } } req2.send(); } function ajax_submit(url, lisp) { document.getElementById("progress").style.display = "block"; var req = new XMLHttpRequest(); req.open("POST", url); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.onreadystatechange = function() { if (req.readyState == 4) { if (req.status == 201) { var jobcount = req.getResponseHeader("X-Job-Count"); var jobelt = document.getElementById("num-jobs") if (jobelt) jobelt.innerHTML = Math.max(jobcount - 1, 0); var loc = req.getResponseHeader("Location"); get_progress(loc); } else { document.getElementById("errors").innerHTML = req.responseText; } } } var content = "formula=" + encodeURIComponent(lisp); req.send(content); } window.addEventListener("load", onload); ================================================ FILE: src/reports/resources/main.css ================================================ html {font-family: sans-serif; font-size: 15px; line-height: 1.2;} body {min-width: 400px; max-width: 650px; margin: 3em auto;} h1, h2, h3 {letter-spacing: .125em; font-weight: 600; margin-top: 4em; } h1 {font-size: 18px; line-height: 1; margin-bottom: .5em;} h2 {font-size: 16px; line-height: 1.125; margin-bottom: .25em; clear: both;} h3 {font-size: 14px; line-height: 1.286; margin-bottom: .2em; clear: both;} p, li, dd, blockquote, figcaption { text-align: justify; -moz-hyphens: auto; -webkit-hyphens: auto; hyphens: auto; margin: .5em 0; } .showcase { background: #ddd; padding: 1em; margin: 5em 0; clear: both;} .showcase figcaption {font-size: 14px; line-height: 1.1; margin-top: 2em;} .before-after { font-size: 24px; text-align: center; line-height: 1.5; } .before-after code { padding: 0 .5ex; display: block; } .before-after .before { color: darkred; } .before-after .after { color: darkgreen; } header {margin: 0 0 4em 0;} header h1 {margin: .5em 0 0 0; font-size: 16pt; text-align: center; } header p {font-size: 16px; margin: .5em 0 0 0; text-align: center;} header img {width: 250px; margin: 0 auto; display: block;} .tool-list { margin-left: 1em; } .tool-list dt {float: left; clear:left; margin-right: 1ex; font-weight: bold; } .tool-list dd {clear: right; margin-bottom: 1em;} .author-list {padding: 0; margin: .5em 1em 3em; } .author-list li {display: inline; margin: 0; white-space: nowrap;} .author-list li:after {content: ","} .author-list li:last-child:after {content: ""} svg {margin: 0 auto; display: block;} pre {padding-left: 2em; font-size: 16px; font-family: monospace;} div.column-container > div {width: 25%; float: left; padding: 0 4%} div.column-container > div > h3 {margin: auto 10px} div.column-container > div > ul {list-style: inside none; margin: 0; padding: 0;} div.column-container:after { content: "."; clear: both; display: block; visibility: hidden; height: 0; } .column-container + .showcase { margin-top: 4em; } ul {padding-left: 1em;} a {color: #2A6496; text-decoration: none} a:hover {text-decoration: underline; color: #295785} #formula textarea, #formula input { width: 100%; font-size: 125%; background-color: white; border: 1px solid rgba(222, 229, 231, 1); border-radius: 2px; } #formula textarea { font-family: monospace; height: 7em; } #input-ranges { margin-top: .1em; } #input-ranges td {padding-top: .4em; } .extra-fields { margin-top: 1em; } .extra-links { float: right; margin-top: .2em; cursor: pointer; } .extra-links a:after { content: "•"; color: black; display: inline-block; padding: 0 1ex; } .extra-links a:last-child:after { content: "»"; padding-right: 0;} #formula .extra-fields label { display: inline-block; width: 20%; line-height: 3; } #formula .extra-fields input, .extra-fields select { width: 80%; font-size: 100%; } #errors li { color: #800; } #formula button[type=submit].hidden { visibility: hidden; height: 0; } #mathjs-instructions, #lis { margin-top: 3em;} #mathjs-instructions-test { width: 100%; font-size: 125%; } input.input-range { width: auto !important; } .input-range-view { margin-top: 15px; margin-bottom: 15px; } .input-range-view .varname { font-size: 125%; } .dropdown { position: relative; display: inline-block; } .dropdown-content { display: none; position: absolute; background-color: #f9f9f9; min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 1; } .dropdown-content div { padding: 5px; } .dropdown-content div:hover {background-color: #f1f1f1} .dropdown-content:hover { display: block; } .dropdown:focus-within .dropdown-content { display: block; } #warnings li { color: #cc5a2a; } #crashed { border: 3px solid #800; background-color: #fdd; font-size: 133%; padding: 1em; } #progress { font-size: 14px; font-family: sans-serif; background-color: #f1f1f1; padding-left: 0; overflow: scroll; overflow-y: auto; } #num-jobs { font-weight: bold; } .function-list dt { font-weight: bold; float: left; width: 200px; clear: left; margin: 0 1em .5em 1em; } .function-list dd { clear: right; margin: 0; } .function-list dd:after { clear: both; height: 1px; display: block; content: "."; visibility: hidden; margin: 0;} .video { display: block; margin: auto; } .abstract { padding: 1em 3em; position: relative; } .abstract:before { content: "“"; font-size: 600%; position: absolute; top: 0; left: 0; font-family: serif; color: #ccc; } .abstract:after { content: "”"; font-size: 600%; position: absolute; bottom: -.5em; right: 0; font-family: serif; color: #ccc; } .paper-thumb { width: 30%; float: left; margin: 1em; } .paper-thumb img { width: 100%; border: 1px solid #ccc; } #news { list-style: none inside; padding: 0; position: relative; } #news li { margin-bottom: 12px; margin-left: 100px; } #news time { font-weight: bold; text-align: right; position: absolute; left: 0; display: block; width: 90px; } #news time:after { content: ":"; } #news .yearmark { border: 0; border-top: 1px solid #bbb; margin: 1em 0; width: 90px; } ================================================ FILE: src/reports/resources/report-page.js ================================================ /* The report page uses a dummy-React pattern, where every time the * page changes we regenerate the whole HTML content from scratch */ // Here is the loaded data: var compareAgainstURL = "" var diffAgainstFields = {} var otherJsonData = null var resultsJsonData = null function update() { const bodyNode = document.querySelector("body"); bodyNode.replaceChildren.apply(bodyNode, buildBody(resultsJsonData, otherJsonData)); } class DeferredCurveCache { constructor(readTests) { this.readTests = readTests; this.key = null; this.curve = null; this.pending = false; } currentKey() { return getCurveCacheKey(this.readTests()); } get() { this.ensure(); const key = this.currentKey(); return this.key === key ? this.curve : null; } ensure() { const key = this.currentKey(); if (this.key === key || this.pending) return; this.pending = true; setTimeout(() => { this.key = this.currentKey(); this.curve = calculateMergedCostAccuracy(this.readTests()); this.pending = false; update(); }, 0); } } // Here is the UI state: const filterNames = { "imp-start": "Improved start", "apx-start": "Approximate start", "uni-start": "Regressed from start", "ex-start": "Exact start", "eq-start": "Equal start", "lt-start": "Less than start", "gt-target": "Greater than target", "gt-start": "Greater than start", "eq-target": "Equal to target", "lt-target": "Less than target", "error": "Error", "timeout": "Timeout", "crash": "Crash", } var filterState = Object.fromEntries(Object.keys(filterNames).map(k => [k, true])); const filterGroups = { improved: [ "ex-start", "eq-start", "eq-target", "imp-start", "gt-target", "gt-start", ], unchanged: [ "lt-target", "apx-start", "error", ], regressed: [ "uni-start", "lt-start", "timeout", "crash", ], }; var filterGroupState = Object.fromEntries(Object.keys(filterGroups).map(k => [k, true])); const radioStates = { output: { title: "Output Expression", tolerance: false, }, startAcc: { title: "Input Accuracy", tolerance: "%", }, endAcc: { title: "Output Accuracy", tolerance: "%", }, targetAcc: { title: "Target Accuracy", tolerance: "%", }, time: { title: "Running Time", tolerance: "s", }, } let radioState = null; // Controlling the diff process var filterTolerance = 1; var hideDirtyEqual = true // Collapsable
    elements var showFilterDetails = false; var showCompareDetails = false; var filterBySuite = "" var filterByWarning = "" var sortState = { key: "name", dir: true, // true = descending, false = ascending } // Next are various strings used in the UI const tempXY_A = "Output vs Input Accuracy" const tempXY_B = "Each point represents a Herbie run below. Its horizontal position shows initial accuracy, and vertical position shows final accuracy. Points above the line are improved by Herbie." const tempPareto_A = "Accuracy vs Speed" const tempPareto_B = "A joint speed-accuracy pareto curve. Accuracy is on the vertical axis, speed is on the horizontal axis. Up and to the right is better. The initial program is shown by the red square." const resultHelpText = `Color key: Green: improved accuracy Light green: no initial error Orange: no accuracy change Red: accuracy worsened Gray: timeout Dark Gray: error` const targetHelpText = `Color key: Dark green: better than target Green: matched target Orange: improved but did not match target Yellow: no accuracy change ` // Helper functions of various sorts function Element(tagname, props, children) { if (children === undefined) { children = props; props = {}; } var $elt = document.createElement(tagname); for (var i in props) if (props.hasOwnProperty(i)) $elt[i] = props[i]; function addAll(c) { if (!c) return; else if (Array.isArray(c)) c.map(addAll); else if (typeof c == "string") $elt.appendChild(document.createTextNode(c)) else if (c instanceof Node) $elt.appendChild(c); else { console.error("Not an element: ", c); throw "Invalid element!" } } addAll(children); return $elt; } function calculatePercent(decimal) { return ((100 - (100 * (decimal)))).toFixed(1) } function formatTime(ms) { if (ms > 60_000) { return (ms / 60_000).toFixed(1) + "min" } else { return (ms / 1000).toFixed(1) + "s" } } function calculateSpeedup(mergedCostAccuracy) { const initial_accuracy = mergedCostAccuracy[0][1] const frontier = mergedCostAccuracy[1] for (const point of [...frontier].reverse()) { if (point[1] > initial_accuracy) { if (typeof point[0] == 'number') { return point[0].toFixed(1) + "×"; } else { return point[0]; } } } } function paretoUnion(curve1, curve2) { const curve1Length = curve1.length; const curve2Length = curve2.length; const output = new Array(curve1Length + curve2Length); let outputLength = 0; let index1 = 0; let index2 = 0; while (index1 < curve1Length && index2 < curve2Length) { const point1 = curve1[index1]; const point2 = curve2[index2]; const cost1 = point1[0]; const error1 = point1[1]; const cost2 = point2[0]; const error2 = point2[1]; if (cost1 === cost2 && error1 === error2) { output[outputLength] = point1; outputLength += 1; index1 += 1; index2 += 1; } else if (cost1 <= cost2 && error1 <= error2) { index2 += 1; } else if (cost1 >= cost2 && error1 >= error2) { index1 += 1; } else if (error1 < error2) { output[outputLength] = point1; outputLength += 1; index1 += 1; } else { output[outputLength] = point2; outputLength += 1; index2 += 1; } } while (index1 < curve1Length) { output[outputLength] = curve1[index1]; outputLength += 1; index1 += 1; } while (index2 < curve2Length) { output[outputLength] = curve2[index2]; outputLength += 1; index2 += 1; } output.length = outputLength; return output; } const PARETO_BLOCK_SIZE = 32; function paretoConvex(points) { const convex = []; for (const point of points) { convex.push(point); while (convex.length >= 3) { const point2 = convex[convex.length - 1]; const point1 = convex[convex.length - 2]; const point0 = convex[convex.length - 3]; const m01 = (point1[1] - point0[1]) / (point1[0] - point0[0]); const m12 = (point2[1] - point1[1]) / (point2[0] - point1[0]); if (m12 <= m01) { break; } convex.splice(convex.length - 2, 1); } } return convex; } function paretoMinimize(points) { const pointsSorted = points.slice().sort((left, right) => left[0] - right[0]); return pointsSorted.reduce((minimized, point) => paretoUnion([point], minimized), []); } function paretoShift([cost0, error0], frontier) { return frontier.map(([cost, error]) => [cost0 + cost, error0 + error]); } function paretoUnionBalanced(curves) { let level = curves; while (level.length > 1) { const nextLevel = []; for (let index = 0; index < level.length; index += 2) { if (index + 1 >= level.length) { nextLevel.push(level[index]); } else { nextLevel.push(paretoUnion(level[index], level[index + 1])); } } level = nextLevel; } return level[0] || []; } // Merge shifted frontiers in small balanced batches. This keeps the // large-large unions that are fast for JS without retaining every // shifted frontier at once. function paretoCombineTwo(combined, frontier) { if (combined.length === 0) { return frontier; } let combinedFrontier = []; for (let index = 0; index < combined.length; index += PARETO_BLOCK_SIZE) { const shiftedFrontiers = []; const limit = Math.min(index + PARETO_BLOCK_SIZE, combined.length); for (let pointIndex = index; pointIndex < limit; pointIndex += 1) { shiftedFrontiers.push(paretoShift(combined[pointIndex], frontier)); } combinedFrontier = paretoUnion(paretoUnionBalanced(shiftedFrontiers), combinedFrontier); } return paretoConvex(combinedFrontier); } function paretoCombine(frontiers) { let level = frontiers.map((frontier) => paretoConvex(paretoMinimize(frontier))); while (level.length > 1) { const nextLevel = []; for (let index = 0; index < level.length; index += 2) { if (index + 1 >= level.length) { nextLevel.push(level[index]); } else { nextLevel.push(paretoCombineTwo(level[index], level[index + 1])); } } level = nextLevel; } return level[0] || []; } function calculateMergedCostAccuracy(tests) { const testsLength = tests.length; const costAccuracies = tests.map((test) => test["cost-accuracy"]); const maximumAccuracy = tests.reduce((sum, test) => sum + test.bits, 0); const initialAccuraciesSum = costAccuracies.reduce((sum, costAccuracy) => { if (!costAccuracy || costAccuracy.length === 0) { return sum; } return sum + costAccuracy[0][1]; }, 0); const initialAccuracy = maximumAccuracy > 0 ? 1 - initialAccuraciesSum / maximumAccuracy : 1.0; const rescaled = costAccuracies .filter((costAccuracy) => costAccuracy && costAccuracy.length > 0) .map((costAccuracy) => { const [initialPoint, bestPoint, otherPoints] = costAccuracy; const initialCost = Number(initialPoint[0]); return [initialPoint, bestPoint].concat(otherPoints).map((point) => [ point[0] / initialCost, point[1], ]); }); const frontier = paretoCombine(rescaled).map(([cost, accuracy]) => { if (cost === 0) { return ["N/A", 1 - accuracy / maximumAccuracy]; } return [testsLength / cost, 1 - accuracy / maximumAccuracy]; }); return [[1.0, initialAccuracy], frontier]; } function getFilteredTests() { return resultsJsonData.tests.filter(filterTest); } function getFilteredBaselineTests() { return getFilteredTests().map(getBaselineTest); } function getCurveCacheKey(tests) { return JSON.stringify(tests.map((test) => test.name)); } const jointCostCache = new DeferredCurveCache(getFilteredTests); const otherJointCostCache = new DeferredCurveCache(getFilteredBaselineTests); //https://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript function toTitleCase(str) { return str.replace( /\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); } ) } function buildDropdown(options, selected, placeholder, onChange) { const select = Element("select", [ Element("option", { value: "", selected: !selected }, [placeholder]), options.map((opt) => Element("option", { value: opt, selected: selected == opt }, [opt]) ), ]); select.addEventListener("change", () => { onChange(select.value); update(); }); return select; } function buildHeader(title) { // The online demo always runs with seed 1; hide the Metrics link there const showMetricsLink = resultsJsonData?.seed != 1 return Element("header", {}, [ Element("h1", {}, title), Element("img", { src: "logo-car.png" }, []), showMetricsLink && Element("nav", {}, [ Element("ul", {}, [ Element("li", {}, [ Element("a", { href: "timeline.html" }, ["Metrics"]) ]) ]) ]), ]) } // Based on https://observablehq.com/@fil/plot-onclick-experimental-plugin // However, simplified because we don't need hit box data function on(mark, listeners = {}) { const render = mark.render mark.render = function (facet, { x, y }, channels) { const data = this.data const g = render.apply(this, arguments) const r = d3.select(g).selectChildren() for (const [type, callback] of Object.entries(listeners)) { r.on(type, function (event, i) { return callback(event, data[i]) }) } return g } return mark } function plotXY(testsData, otherJsonData) { const filteredTests = getFilteredTests(); function onclick(e, d) { window.location = d.link + "/graph.html"; } let marks = [ Plot.line([[0, 0], [1, 1]], { stroke: '#ddd' }), ]; if (otherJsonData) { marks.push(Plot.arrow(filteredTests, { x1: d => 1 - getBaselineTest(d).start / 64, y1: d => 1 - getBaselineTest(d).end / 64, x2: d => 1 - d.start / 64, y2: d => 1 - d.end / 64, stroke: "#900", strokeWidth: 2, })); } marks.push(on(Plot.dot(filteredTests, { x: d => 1 - d.start / 64, y: d => 1 - d.end / 64, fill: "#00a", strokeWidth: 2, }), { click: onclick })); const out = Plot.plot({ marks: marks, className: "clickable", marginBottom: 0, marginRight: 0, width: '400', height: '400', x: { nice: true, line: true, tickFormat: "%", }, y: { nice: true, line: true, tickFormat: "%", }, }) out.setAttribute('viewBox', '0 0 420 420') return out; } function plotPareto(jsonData, otherJsonData) { const mergedCostAccuracy = jointCostCache.get(); if (mergedCostAccuracy === null) { return Element("div", { style: "max-width: 100%; aspect-ratio: 1;", "aria-label": "Pareto plot loading", }, []); } const [initial, frontier] = mergedCostAccuracy; let marks = [ Plot.dot([initial], { stroke: "#00a", symbol: "square", strokeWidth: 2, }), Plot.line(frontier, { stroke: "#00a", strokeWidth: 2, }), ]; if (otherJsonData) { const otherMergedCostAccuracy = otherJointCostCache.get(); if (otherMergedCostAccuracy === null) { return Element("div", { style: "max-width: 100%; aspect-ratio: 1;", "aria-label": "Pareto plot loading", }, []); } const [initial2, frontier2] = otherMergedCostAccuracy; marks = [ Plot.dot([initial2], { stroke: "#900", symbol: "square", strokeWidth: 2, }), Plot.line(frontier2, { stroke: "#900", strokeWidth: 2, }) ].concat(marks); } const out = Plot.plot({ marks: marks, width: '400', height: '400', x: { line: true, nice: true, tickFormat: c => c + "×" }, y: { line: true, nice: true, domain: [0, 1], tickFormat: "%", }, marginBottom: 0, marginRight: 0, }) out.setAttribute('viewBox', '0 0 420 420') return out; } function buildCheckboxLabel(classes, text, boolState) { return Element("label", { classList: classes }, [ Element("input", { type: "checkbox", checked: boolState }, []), text, ]); } function buildDiffLine() { const urlInput = Element("input", { id: "compare-input", value: compareAgainstURL, placeholder: "URL to report or JSON file", size: 60, }, []); var unitText = radioStates[radioState]?.tolerance; const submitButton = Element("button", "Compute Diff") submitButton.addEventListener("click", async (e) => { e.preventDefault(); radioState = radioState ?? "endAcc"; otherJsonData = await fetchBaseline(urlInput.value); update(); }); const toleranceInputField = Element("input", { id: `toleranceID`, value: filterTolerance, size: 10, style: "text-align:right;", }, []); toleranceInputField.addEventListener("change", () => { filterTolerance = toleranceInputField.value; update(); }); return [ urlInput, unitText && [" Hiding: ±", toleranceInputField, unitText], " ", submitButton ]; } function buildCompareForm() { const formName = "compare-form" let radioButtons = []; for (let [i, stateInfo] of Object.entries(radioStates)) { let radioElt = Element("input", { name: formName, id: "compare-" + i, type: "radio", checked: radioState == i, }, []); radioElt.addEventListener("click", () => { radioState = i; update(); }); let labelElt = Element("label", [radioElt, stateInfo.title]); radioButtons.push(labelElt); } const hideEqual = buildCheckboxLabel("hide-equal", "Hide equal", hideDirtyEqual) hideEqual.addEventListener("click", () => { hideDirtyEqual = ! hideDirtyEqual; update(); }) return Element("form", {}, [radioButtons, " ", hideEqual]); } function summarizeTests(tests) { return tests.reduce((acc, test) => { acc.totalStart += test.start; acc.totalEnd += test.end; acc.maxAccuracy += test.bits; acc.totalTime += test.time; if (test.status == "timeout" || test.status == "crash") { acc.crashCount += 1; } return acc; }, { totalStart: 0, totalEnd: 0, maxAccuracy: 0, totalTime: 0, crashCount: 0 }); } function buildStats(jsonData, mergedCostAccuracy) { const summary = summarizeTests(jsonData.tests); const speedup = mergedCostAccuracy === null ? "⋯" : calculateSpeedup(mergedCostAccuracy); return Element("div", { id: "large" }, [ Element("div", {}, [ "Average Percentage Accurate: ", Element("span", { classList: "number" }, [ calculatePercent(summary.totalStart / summary.maxAccuracy), "%", Element("span", { classList: "unit" }, [" → ",]), calculatePercent(summary.totalEnd / summary.maxAccuracy), "%" ]), ]), Element("div", {}, [ "Time:", Element("span", { classList: "number" }, [formatTime(summary.totalTime)]) ]), Element("div", {}, [ "Bad Runs:", Element("span", { classList: "number", title: "Crashes and timeouts are considered bad runs." }, [`${summary.crashCount}/${jsonData.tests.length}`]) ]), Element("div", {}, [ "Speedup:", Element("span", { classList: "number", title: "Aggregate speedup of fastest alternative that improves accuracy." }, [speedup]) ]), ]); } function buildTableHeader(stringName, help) { const textElement = Element("th", {}, [ toTitleCase(stringName), " ", (stringName != sortState.key ? "–" : sortState.dir ? "⏶" : "⏷"), help && Element("span", { classList: "help-button", title: help }, ["?"]), ]); textElement.addEventListener("click", () => { if (stringName == sortState.key) { sortState.dir = !sortState.dir; } else { sortState.key = stringName; sortState.dir = true; } update(); }) return textElement } function buildBody(jsonData, otherJsonData) { const mergedCostAccuracy = jointCostCache.get(); const stats = buildStats(jsonData, mergedCostAccuracy); const header = buildHeader("Herbie Results") const figureRow = Element("div", { classList: "figure-row" }, [ Element("figure", { id: "xy" }, [ Element("h2", {}, [tempXY_A]), plotXY(jsonData, otherJsonData), Element("figcaption", {}, [tempXY_B]) ]), Element("figure", { id: "pareto" }, [ Element("h2", {}, [tempPareto_A]), plotPareto(jsonData, otherJsonData), Element("figcaption", {}, [tempPareto_B]) ]) ]) const rows = buildTableContents(jsonData) const footer = buildDiffFooter(jsonData, otherJsonData) const resultsTable = Element("table", { id: "results" }, [ Element("thead", {}, [ Element("tr", {}, [ buildTableHeader("name"), buildTableHeader("start"), buildTableHeader("end", resultHelpText), buildTableHeader("target", targetHelpText), buildTableHeader("time"), ]), ]), rows, footer ]); return [header, stats, figureRow, buildControls(jsonData, otherJsonData, rows.length), resultsTable] } function compareTests(l, r) { let cmp; if (sortState.key == "name") { cmp = l.name.localeCompare(r.name); } else { if (l[sortState.key] === false) { cmp = 1; } else if (r[sortState.key] === false) { cmp = -1; } else { cmp = l[sortState.key] - r[sortState.key]; } } if (sortState.dir) cmp = -cmp; return cmp; } function getBaselineTest(test) { return diffAgainstFields[test.name] } function getVisibleTests(jsonData) { const visibleTests = [] for (const test of [...jsonData.tests].sort(compareTests)) { if (filterTest(test)) visibleTests.push(test); } return visibleTests } function buildTableContents(jsonData) { const visibleTests = getVisibleTests(jsonData); return visibleTests.map((test) => buildRow(test, getBaselineTest(test))); } function computeDiffTotal(jsonData) { if (!otherJsonData || !radioState) return 0; let total = 0; for (let test of jsonData.tests) { let other = getBaselineTest(test); if (!other) continue; if (!filterTest(test)) continue; if (radioState == "startAcc") { const cur = calculatePercent(test.start / test.bits); const base = calculatePercent(other.start / other.bits); total += cur - base; } else if (radioState == "endAcc") { const cur = calculatePercent(test.end / test.bits); const base = calculatePercent(other.end / other.bits); total += cur - base; } else if (radioState == "targetAcc") { const curMin = getMinimum(test.target); const baseMin = getMinimum(other.target); if (curMin !== false && baseMin !== false) { total += calculatePercent(curMin / test.bits) - calculatePercent(baseMin / other.bits); } } else if (radioState == "time") { total += other.time - test.time; } } return total; } function buildDiffFooter(jsonData, otherJsonData) { if (!otherJsonData || !radioState) return []; const total = computeDiffTotal(jsonData); let color = "diff-time-gray"; let text = "~"; if (radioState == "time") { if (Math.abs(total) > filterTolerance * 1000) { if (total > 0) { color = "diff-time-green"; text = "+ " + formatTime(total); } else { color = "diff-time-red"; text = "-" + formatTime(Math.abs(total)); } } } else { if (Math.abs(total.toFixed(1)) > filterTolerance) { if (total > 0) { color = "diff-time-green"; text = "+ " + total.toFixed(1) + "%"; } else { color = "diff-time-red"; text = "-" + Math.abs(total).toFixed(1) + "%"; } } } const cells = [ Element("th", {}, ["Total"]), radioState == "startAcc" ? Element("td", { classList: color }, [text]) : Element("td", {}, []), radioState == "endAcc" ? Element("td", { classList: color }, [text]) : Element("td", {}, []), radioState == "targetAcc" ? Element("td", { classList: color }, [text]) : Element("td", {}, []), radioState == "time" ? Element("td", { classList: color }, [text]) : Element("td", {}, []), Element("td", {}, []), Element("td", {}, []), ]; return Element("tfoot", {}, [Element("tr", {}, cells)]); } function getMinimum(target) { if (target === false) { return false } return target.reduce((minA, current) => { const currentA = current[1]; return currentA < minA ? currentA : minA; }, Infinity); } // Builds either a normal run row or a comparison row when `other` is present. function buildRow(test, other) { var smallestTarget = getMinimum(test.target) if (!other) { var startAccuracy = calculatePercent(test.start / test.bits) + "%" var resultAccuracy = calculatePercent(test.end / test.bits) + "%" var targetAccuracy = calculatePercent(smallestTarget / test.bits) + "%" if (test.status == "imp-start" || test.status == "ex-start" || test.status == "apx-start") { targetAccuracy = "" } if (test.status == "timeout" || test.status == "error") { startAccuracy = "" resultAccuracy = "" targetAccuracy = "" } const tr = Element("tr", { classList: test.status }, [ Element("td", {}, [test.name]), Element("td", {}, [startAccuracy]), Element("td", {}, [resultAccuracy]), Element("td", {}, [targetAccuracy]), Element("td", {}, [formatTime(test.time)]), Element("td", {}, [ Element("a", { href: `${test.link}/graph.html` }, ["»"])]), Element("td", {}, [ Element("a", { href: `${test.link}/timeline.html` }, ["📊"])]), ]) // TODO fix bug with cmd/ctrl click. tr.addEventListener("click", () => tr.querySelector("a").click()) return tr; } else { function timeTD(test) { var timeDiff = test.time - other.time var color, text if (timeDiff > filterTolerance * 1000) { color = "diff-time-red"; text = "-" + formatTime(timeDiff); } else if (timeDiff < -filterTolerance * 1000) { color = "diff-time-green"; text = "+ " + formatTime(Math.abs(timeDiff)); } else { color = "diff-time-gray"; text = "~"; } var titleText = `current: ${formatTime(test.time)} vs ${formatTime(other.time)}` return Element("td", { classList: color, title: titleText }, [text]); } function accuracyTD(testVal, otherVal) { const op = calculatePercent(otherVal / other.bits) const tp = calculatePercent(testVal / test.bits) let diff = op - tp let color = "diff-time-red" let tdText = `- ${diff.toFixed(1)}%` if (Math.abs(diff.toFixed(1)) <= filterTolerance) { color = "diff-time-gray" tdText = "~" } else if (diff < 0) { diff = Math.abs(diff) color = "diff-time-green" tdText = `+ ${diff.toFixed(1)}%` } const titleText = `Original: ${op} vs ${tp}` return Element("td", { classList: color, title: titleText }, [tdText]) } const startAccuracy = accuracyTD(test.start, other.start) const resultAccuracy = accuracyTD(test.end, other.end) const targetAccuracy = accuracyTD(smallestTarget, other.target) var tdStartAccuracy = radioState == "startAcc" ? startAccuracy : Element("td", {}, [calculatePercent(test.start / test.bits), "%"]) var tdResultAccuracy = radioState == "endAcc" ? resultAccuracy : Element("td", {}, [calculatePercent(test.end / test.bits), "%"]) var tdTargetAccuracy = radioState == "targetAcc" ? targetAccuracy : Element("td", {}, [calculatePercent(smallestTarget / test.bits), "%"]) const tdTime = radioState == "time" ? timeTD(test) : Element("td", {}, [formatTime(test.time)]) var testTitle = "" if (test.output != other.output) { testTitle = `Current output:\n${test.output} \n \n Comparing to:\n ${other.output}` } if (test.status == "imp-start" || test.status == "ex-start" || test.status == "apx-start") { tdTargetAccuracy = Element("td", {}, []) } if (test.status == "timeout" || test.status == "error") { tdStartAccuracy = Element("td", {}, []) tdResultAccuracy = Element("td", {}, []) tdTargetAccuracy = Element("td", {}, []) } const tr = Element("tr", { classList: test.status }, [ Element("td", { title: testTitle }, [test.name]), tdStartAccuracy, tdResultAccuracy, tdTargetAccuracy, tdTime, Element("td", {}, [ Element("a", { href: `${test.link}/graph.html` }, ["»"])]), Element("td", {}, [ Element("a", { href: `${test.link}/timeline.html` }, ["📊"])]), ]) tr.addEventListener("click", () => tr.querySelector("a").click()) return tr; } } function buildDiffControls() { var summary = Element("details", { open: showCompareDetails }, [ Element("summary", {}, [ Element("h2", {}, ["Diff"]), buildDiffLine(), ]), buildCompareForm(), ]) summary.addEventListener("toggle", () => { showCompareDetails = summary.open; }); return summary; } function buildControls(jsonData, otherJsonData, diffCount) { var displayingDiv = Element("div", [ "Displaying " + diffCount + "/" + jsonData.tests.length + " benchmarks", " on ", Element("code", jsonData.branch), otherJsonData && [ ", compared with baseline ", Element("code", otherJsonData.branch), ], ]) return Element("div", { classList: "report-details" }, [ displayingDiv, buildDiffControls(), buildFilterControls(jsonData), ]) } function buildFilterGroup(name) { let subFilters = filterGroups[name]; let label = buildCheckboxLabel(name, toTitleCase(name), filterGroupState[name]); label.addEventListener("click", (e) => { filterGroupState[name] = e.target.checked; for (let filterName of subFilters) { filterState[filterName] = e.target.checked; } update(); }); return label; } function countTestsByStatus(tests) { const counts = {} for (const test of tests) { counts[test.status] = (counts[test.status] ?? 0) + 1 } return counts } function collectSuites(tests) { const suites = new Set() for (const test of tests) { const linkComponents = test.link.split("/") if (linkComponents.length > 1) { suites.add(linkComponents[0]) } } return [...suites] } function collectWarnings(tests) { const warnings = new Set() for (const test of tests) { for (const warning of test.warnings) { warnings.add(warning) } } return [...warnings] } function buildFilterControls(jsonData) { const testTypeCounts = countTestsByStatus(jsonData.tests) var filterButtons = [] for (let f in filterState) { const name = `${filterNames[f]} (${testTypeCounts[f] ?? 0})` const button = buildCheckboxLabel(f + " sub-filter", name, filterState[f]) button.addEventListener("click", () => { filterState[f] = button.querySelector("input").checked update() }) filterButtons.push(button) } const suiteDropdown = buildDropdown( collectSuites(jsonData.tests), filterBySuite, "Filter by suite", (value) => { filterBySuite = value; }, ); const warningDropdown = buildDropdown( collectWarnings(jsonData.tests), filterByWarning, "Filter to warning", (value) => { filterByWarning = value; }, ); let groupButtons = []; for (let i in filterGroupState) { groupButtons.push(buildFilterGroup(i)); } const filters = Element("details", { id: "filters", open: showFilterDetails }, [ Element("summary", {}, [ Element("h2", {}, "Filters"), groupButtons, " ", suiteDropdown, " ", warningDropdown, ]), filterButtons, ]); filters.addEventListener("toggle", () => { showFilterDetails = filters.open; }); return filters; } function showGetJsonError() { const header = buildHeader("Error loading results") const is_windows = navigator.userAgent.indexOf("Windows") !== -1; const page_name = window.location.pathname.split("/").at(-1); let page_location; if (is_windows) { page_location = window.location.pathname.split("/").slice(1, -1).join("\\"); } else { page_location = window.location.pathname.split("/").slice(0, -1).join("/"); } let reason; if (window.location.protocol == "file:") { reason = [ Element("p", [ "Modern browsers prevent access over ", Element("code", "file://"), " URLs, which Herbie requires.", ]), Element("p", [ "You can work around this by starting a local server, like so:" ]), Element("pre", [ is_windows // Python on windows is usually called python, not python3 ? "python -m http.server -d " + page_location + " 8123" : "python3 -m http.server -d " + page_location + " 8123" ]), Element("p", [ "and then navigating to ", Element("a", { href: "http://localhost:8123/" + page_name, }, Element("kbd", "http://localhost:8123/" + page_name)), ]), ]; } const message = Element("section", {className: "error"}, [ Element("h2", "Could not load results"), reason, ]); const body = [header, message]; const bodyNode = document.querySelector("body"); if (bodyNode) { bodyNode.replaceChildren.apply(bodyNode, body); } else { document.addEventListener("DOMContentLoaded", showGetJsonError); } } function filterDirtyEqual(baseData, diffData) { if (!hideDirtyEqual) { return false } if (radioState == "output") { return baseData.output == diffData.output } if (radioState == "startAcc") { const t = baseData.start / baseData.bits const o = diffData.start / diffData.bits const op = calculatePercent(o) const tp = calculatePercent(t) const diff = op - tp return Math.abs((diff).toFixed(1)) <= filterTolerance } if (radioState == "endAcc") { const t = baseData.end / baseData.bits const o = diffData.end / diffData.bits const op = calculatePercent(o) const tp = calculatePercent(t) const diff = op - tp return Math.abs((diff).toFixed(1)) <= filterTolerance } if (radioState == "targetAcc") { const smallestBase = getMinimum(baseData.target) const smallestDiff = getMinimum(diffData.target) const t = smallestBase / baseData.bits const o = smallestDiff / diffData.bits const op = calculatePercent(o) const tp = calculatePercent(t) const diff = op - tp return Math.abs((diff).toFixed(1)) <= filterTolerance } if (radioState == "time") { const timeDiff = baseData.time - diffData.time return Math.abs(timeDiff) < (filterTolerance * 1000) } return false } function filterSuite(baseData) { const linkComponents = baseData.link.split("/") return filterBySuite && linkComponents.length > 1 && // defensive lowerCase filterBySuite.toLowerCase() != linkComponents[0].toLowerCase() } function filterWarning(baseData) { return filterByWarning && baseData.warnings.indexOf(filterByWarning) === -1 } function filterStatus(baseData) { return !filterState[baseData.status] } function filterTest(baseData) { const diffData = getBaselineTest(baseData) if (filterDirtyEqual(baseData, diffData)) return false if (filterSuite(baseData)) return false if (filterWarning(baseData)) return false if (filterStatus(baseData)) return false return true } async function fetchBaseline(url) { if (!url) return; if (url.endsWith("/")) url += "results.json"; compareAgainstURL = url; const response = await fetch(url, { headers: { "content-type": "text/plain" }, method: "GET", mode: "cors", }); const json = await response.json() if (json.error) return; diffAgainstFields = Object.fromEntries(json.tests.map((test) => [test.name, test])); return json; } async function getResultsJson() { if (resultsJsonData == null) { try { const response = await fetch("results.json", { headers: { "content-type": "application/json" }, }); resultsJsonData = (await response.json()); } catch (err) { return showGetJsonError(); } update(); } } getResultsJson() ================================================ FILE: src/reports/resources/report.css ================================================ /* Standard Herbie header */ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans&family=Ruda:wght@400;500&display=swap'); html { font-family: 'IBM Plex Serif', serif; font-size: 16px; line-height: 1.4; } body { max-width: 800px; margin: .5em auto 3em; } a { color: #2A6496; text-decoration: none; cursor: pointer } a:hover {text-decoration: underline; color: #295785} header { line-height: 2; border-bottom: 1px solid #ddd; margin-bottom: 2em; display: flex; flex-direction: row; align-items: bottom; font-family: 'Ruda', serif; } header h1 { width: calc(50% - 54px/2); margin: 0; font-size: 125%; line-height: 1.6 } header img { width: 54px; height: 24px; vertical-align: bottom; padding-bottom: 4px; } header nav { width: calc(50% - 54px/2); text-align: right; } header ul { margin: 0; padding: 0; font-weight: bold; } header li { display: inline-block; margin: 0 .5em; } header li::before { content: "•"; margin-right: 1em; } header li:first-child::before { content: none; } summary h1, summary h2 { display: inline-block; } #large { margin: 2em 0; display: flex; gap: 3em; justify-content: center; font-family: 'IBM Plex Serif', serif; color: #888; } #large .number { font-size: 2em; display: block; font-family: 'Ruda', serif; color: black; margin-top: -.2em; } #large .unit { font-family: 'IBM Plex Serif', serif; color: #aaa; } @media print { #large { margin-top: 0; }} /* Plots at the top */ .figure-row { display: flex; flex-direction: row; gap: 1em; align-items: start; } figure, section { flex: 1; border: 1px solid #ddd; padding: 1ex; margin: 1em 0; align-self: start; } section.error { border: 2px solid #a40000; background: #ef2929; color: white; } section.error a { color: #ffdb4c; } h2 { font-size: 1em; font-family: 'IBM Plex Serif', serif; margin: 0; } data { font-family: 'Ruda'; } h2 .subhead { font-weight: normal; } svg .domain, svg .tick { color: #888; } svg text { font-family: 'IBM Plex Serif', serif; } figcaption { font-size: 80%; color: #666; padding-top: 1ex; } svg.clickable circle:hover { stroke: #00a; stroke-width: 10; cursor: pointer; } /* Flag list / configuration */ #about { margin: 3em 0; } #about th { font-weight: bold; text-align: left; padding-right: 1em; } #flag-list { position: relative; } #flag-list kbd:not(:last-child):after { content: ", "; } #flag-list a { float: right; margin: 0 .5em; } #flag-list a:before { content: "("; } #flag-list a:after { content: ")"; } #flag-list #changed-flags { display: none; } #flag-list #all-flags { display: block; } #flag-list.changed-flags #changed-flags { display: block; } #flag-list.changed-flags #all-flags { display: none; } /* Result color pallette */ :root { --accent-text: #4a4a4a; --accent-title: #888; --accent-board: #ddd; --result-green: #87fc70; --result-light-green: #e0f8d8; --result-light-orange: #ff9500; --result-orange: #ff9500; --result-red-orange: #ff5e3a; --result-red: #ff0000; --result-yellow: #ffdb4c; --result-timeout: #8e8e93; --result-error: #4a4a4a; --accent-blue: #0000aa; } /* Result table filters */ label.imp-start {accent-color: var(--result-green);} label.apx-start {accent-color: var(--result-orange);} label.uni-start {accent-color: var(--result-red-orange);} label.ex-start {accent-color: var(--result-light-green);} label.eq-start {accent-color: var(--result-green);} label.lt-start {accent-color: var(--result-yellow);} label.gt-target {accent-color: var(--result-green);} label.gt-start {accent-color: var(--result-green);} label.eq-target {accent-color: var(--result-green);} label.lt-target {accent-color: var(--result-orange);} label.crash {accent-color: var(--result-red);} label.error {accent-color: var(--result-error);} label.timeout {accent-color: var(--result-timeout);} #filters label {margin: 0; display: inline-block; padding: .4ex; color: var(--accent-text);} #filters summary {accent-color: var(--accent-blue);} #filters .sub-filter {column-width: 11.5rem;} /* Compare Reports */ .diff-time-red {color: red;} .diff-time-green {color: green;} .diff-time-gray {color: gray;} .diff-status {color: #0000aa;} .report-details {border: 1px solid var(--accent-board); padding: 1ex; margin: 0 0 1em 0;} .report-details h2 {font-size: 16px;} .report-details h3 {font-size: 14px;} .report-details h2, h3 {color: var(--accent-title); line-height: 1.4;} .report-details form { display: inline;} .report-details > details summary h2 {padding-right: 1em;} .report-details > details > div > h3 {margin: 0; display: inline; padding-right: 1em; color: var(--accent-title);} /* Table of results */ #results { border-collapse: collapse; width:100%; } #results th, #results td { border: 1px solid #ddd; padding: .5em; } #results th { white-space: pre; color: #888; font-family: 'IBM Plex Serif', serif; } #results td { text-align: right; overflow: hidden; font-size: 15pt; font-family: 'Ruda', serif; font-weight: 500; } #results tbody tr:hover { background-color: #f8f8f8; cursor: pointer; } #results td:nth-child(1) { text-align: left; word-break: break-all; } #results td:nth-child(6) {width: 0;} tr.imp-start td:nth-child(3) {background-color:#87fc70;} tr.apx-start td:nth-child(3) {background-color:#ff9500;} tr.uni-start td:nth-child(3) {background-color:#ff5e3a;color:#f7f7f7;} tr.ex-start td:nth-child(3) {background-color:#e0f8d8;} tr.gt-target td:nth-child(3) {background-color:#87fc70;} tr.gt-target td:nth-child(4) {background-color:#0bd318;} tr.eq-target td:nth-child(3) {background-color:#87fc70;} tr.eq-target td:nth-child(4) {background-color:#87fc70;} tr.lt-target td:nth-child(3) {background-color:#87fc70;} tr.lt-target td:nth-child(4) {background-color:#ff9500;} tr.eq-start td:nth-child(3) {background-color:#ff9500;} 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:#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;} /* Help button */ .help-button { display: inline-block; background: #999; font-size: .8em; color: #eee; line-height: 1.3em; height: 1.25em; width: 1.25em; border-radius: .625em; text-align: center; margin-left: .5ex; position: relative; bottom: .125em; } .help-button:hover { background: #444; color: #eee; text-decoration: none; cursor: pointer; } .help-button.float { float: right; font-size: 120%; font-weight: normal; } /* Detail page layout */ section { display: flow-root; } /* Warnings */ .warnings { list-style: inside none; padding: 0; } .warnings > li { margin: .25em 0; padding: .5em; background: #ffdb4c; border: 2px solid #ff9500; overflow: hidden; } .warnings h2 { font-size: 100%; font-weight: normal; margin: 0; } .warnings h2:before { content: "Warning: "; font-weight: bold; } .warnings h2 a { float: right; } .warnings ol { list-style: inside none; padding: 0 1em; } .warnings ol li { margin: .25em 0; } /* Big block for program input output */ .programs { position: relative; } .programs select { position: absolute; right: 3em; } .program { display: inline-block; text-align: left; } #precondition { margin: 2em 0; } #precondition:before { content: "Precondition:"; display: block; color: #444; margin-left: -2em; } /* Allow line breaks in display equations (see https://katex.org/docs/issues.html) */ .katex-display > .katex { white-space: normal } .katex-display .base { margin: 0.25em 0 } .katex-display { margin: 0.5em 0; } .katex-display .arraycolsep { width: 0 !important; } #preprocess { padding: 0 1em 1em; margin: 0 -1em 1em; border-bottom: 2px solid white; } #preprocess:before { content: "Preprocess"; float: left; color: #444; } /* Error graphs */ #functions { display: flex; flex-direction: row; gap: 1em; margin-top: 1ex; } #functions:before { content: "Show: "; } #cost-accuracy p { font-size: 1em; font-family: 'IBM Plex Serif', serif; margin: 0; } #cost-accuracy table { width: 374px; padding: 1ex 0; } #cost-accuracy thead th { color: #888; border-bottom: 1px solid #888; } #cost-accuracy thead th.numeric { text-align: right; } #cost-accuracy tbody th { font-weight: normal; } #cost-accuracy tbody td { font-family: 'Ruda', serif; text-align: right; font-weight: 400; } #cost-accuracy tbody td.better { font-weight: 800; color: #050; } /* Try it out section */ #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; } /* Derivation */ .history, .history ol { list-style: none inside; width: 800px; margin: 0 0 2em; padding: 0; } .history li p { width: 250px; display: inline-block; margin: 0 25px 0 0; } .history li .error { display: block; color: #666; } .history li > div { display: inline-block; margin: 0; width: 500px; vertical-align: middle; } .history li > div > div { margin: 0; display: inline-block; text-align: right !important; } .history h2 { margin: 1.333em 0 .333em; } .history li { margin: .5em 0; border-top: 1px solid #ddd; padding-top: .5em; } .history li:first-child { border-top: none; padding-top: 0; } .history .rule { text-decoration: underline; } .history .event { display: block; margin: .5em 0; } .history .proof { width: 100%; overflow: auto; } .history .proof table { padding: 0 .5em; } .history .proof table th { text-align: left; font-weight: normal; } .history .proof table th .info { display: block; color: #666; } .history .proof table td { text-align: left; } /* Process / debug info */ #process-info { background: #ddd; padding: 0; } #process-info p.header { font-size: 110%; padding: 1em 1em .5em; margin: 0; } .timeline { height: 2em; border: 1px solid #888; border-width: 1px 0px; margin-bottom: 1em; display: flex; } .timeline-phase { height: 2em; background: var(--phase-color); display: inline-block; } @media print { .timeline { border: none; } .timeline-phase { outline: 1px solid black; } } /* Blocks of information */ .timeline-block { border-left: 1ex solid var(--phase-color); padding: 1px 1ex;} .timeline-block h3 { margin: 0; font-size: 110%; font-weight: normal; } .timeline-block p { margin: 0; } .timeline-block h3 .time { float: right; } .timeline-block dl { font-size: 90%; } .timeline-block dt { min-width: 6em; float: left; font-size: 100%; } .timeline-block dd { margin: 0 0 1ex 6em; max-width: 100%; overflow: auto; } table.times { border-spacing: 15px 5px; } table th { text-align: left; } table.times td { text-align: right; min-width: 8ex; vertical-align: baseline; } table.times td:last-child { text-align: left; } table.states { min-width: 50%; } table.states td:last-child, table.states tfoot { font-weight: bold; } table pre { padding: 0; margin: 0; text-align: left; font-size: 110%; } /* Timeline colors -- Uses Tango color scheme */ .timeline-sample { --phase-color: #edd400; } .timeline-analyze { --phase-color: #fce94f; } .timeline-eval { --phase-color: #204a87; } .timeline-prune { --phase-color: #3465a4; } .timeline-reconstruct { --phase-color: #729fcf; } .timeline-gc { --phase-color: #888a85; } .timeline-localize { --phase-color: #729fcf; } .timeline-rewrite { --phase-color: #4e9a06; } .timeline-simplify { --phase-color: #73d216; } .timeline-derivations{ --phase-color: #8ae234; } .timeline-preprocess { --phase-color: #5c3566; } .timeline-series { --phase-color: #ad7fa8; } .timeline-regimes { --phase-color: #a40000; } .timeline-bsearch { --phase-color: #cc0000; } /* Bogosity colors */ .bogosity { display: flex; margin-bottom: 1em; height: 1em; border-radius: .5em; overflow: clip; } .bogosity > div { background: var(--phase-color); display: inline-block; } @media print { .bogosity { border: none; } .bogosity > div { outline: 1px solid black; } } .bogosity-valid { --phase-color: #4e9a06; } .bogosity-infinite { --phase-color: #8ae234; } .bogosity-unknown { --phase-color: #000000; } .bogosity-unsamplable { --phase-color: #a40000; } .bogosity-invalid { --phase-color: #204a87; } .bogosity-precondition { --phase-color: #729fcf; } /* Code sample to reproduce */ pre.shell code:before { content: "$ "; font-weight: bold; } /* Target / expected code block */ #comparison table { width: 300px; display: inline-table; vertical-align: top; } #comparison table th { text-align: left; } #comparison table td { text-align: right; } #comparison div { display: inline-block; width: 500px; } /* Formatting backtraces */ #backtrace table { width: 100%; } #backtrace th[colspan] { text-align: left; } #backtrace th { text-align: right; } #backtrace td:nth-child(3), #backtrace td:nth-child(4) { text-align: right; } #backtrace td.procedure { font-family: monospace; } /* Formatting profile info */ .loaded .load-text { display: none; } .load-text { text-align: center; font-size: 140%; color: #888; } a.delete { color: currentColor; } a.delete:hover { color: #ed2b00; text-decoration: line-through; } #profile { border: 1px solid #ddd; } #profile input { width: 100%; padding: .5ex 1ex; box-sizing: border-box; border: none; border-bottom: 1px solid #ddd; font-size: 150%; } #profile .profile-row { margin: 1em 0; } .profile-row div { display: flex; } #profile .edge { color: #888; margin-left: 2em; } #profile .node { font-size: 120%; } #profile .path, #profile .name { font-family: monospace; white-space: nowrap; } #profile .path { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; } #profile .path:before { content: " ("; } #profile .path:after { content: ") "; } .profile-row > div > * { padding: 0 .5em; } #profile .pct { width: 4em; text-align: right; } ================================================ FILE: src/reports/resources/report.html ================================================ Herbie results ================================================ FILE: src/reports/resources/report.js ================================================ window.COMPONENTS = [] function Component(selector, fns) { this.selector = selector; this.fns = fns; window.COMPONENTS.push(this); } function ComponentInstance(elt, component) { for (var i in component.fns) { if (component.fns.hasOwnProperty(i)) { this[i] = component.fns[i].bind(this); } } this.elt = elt; } function Element(tagname, props, children) { if (children === undefined) { children = props; props = {}; } var $elt = document.createElement(tagname); for (var i in props) if (props.hasOwnProperty(i)) $elt[i] = props[i]; function addAll(c) { if (!c) return; else if (Array.isArray(c)) c.map(addAll); else if (typeof c == "string") $elt.appendChild(document.createTextNode(c)) else if (c instanceof Node) $elt.appendChild(c); else { console.error("Not an element: ", c); throw "Invalid element!" } } addAll(children); return $elt; } var TogglableFlags = new Component("#flag-list", { setup: function() { this.elt.classList.add("changed-flags"); this.button = Element("a", {id: "flag-list-toggle"}, "see all"); this.button.addEventListener("click", this.toggle); this.elt.insertBefore(this.button, this.elt.children[0]); }, toggle: function() { this.elt.classList.toggle("changed-flags"); var changed_only = this.elt.classList.contains("changed-flags"); this.button.innerText = changed_only ? "see all" : "see diff"; } }); // Cicular color wheel representing error values limited to size 10 const colors = [ { line: { stroke: '#d00' }, dot: { stroke: '#d002'} }, { line: { stroke: '#00a' }, dot: { stroke: '#00a2'} }, { line: { stroke: '#080' }, dot: { stroke: '#0802'} }, { line: { stroke: '#0d0' }, dot: { stroke: '#0d02'} }, { line: { stroke: '#a00' }, dot: { stroke: '#a002'} }, { line: { stroke: '#0a0' }, dot: { stroke: '#0a02'} }, { line: { stroke: '#00d' }, dot: { stroke: '#00d2'} }, { line: { stroke: '#008' }, dot: { stroke: '#0082'} }, { line: { stroke: '#d80' }, dot: { stroke: '#d802'} }, { line: { stroke: '#00f' }, dot: { stroke: '#00f2'} }, { line: { stroke: '#f00' }, dot: { stroke: '#f002'} } ]; const ClientGraph = new Component('#graphs', { setup: async function() { const points = await fetch("points.json", { headers: {"content-type": "text/plain"}, method: "GET", mode: 'cors' }); this.points_json = await points.json(); this.all_vars = this.points_json.vars; this.$variables = this.elt.querySelector("#variables"); this.$functions = this.elt.querySelector("#functions"); await this.render(this.all_vars[0], ['start', 'end']); }, render_variables: function($elt, selected_var_name, selected_functions) { $elt.replaceChildren( Element("select", { oninput: (e) => this.render(e.target.value, selected_functions), }, this.all_vars.map(v => Element("option", { value: v, selected: selected_var_name == v, }, v) ))); }, render_functions: function($elt, selected_var_name, selected_functions) { const toggle = (option, options) => options.includes(option) ? options.filter(o => o != option) : [...options, option] // this.points_json.error is a dictionary where keys are indices "1", "2", ..., "n". // The values for each key is broken up into two parts : error_type and actual error values // error_type is the first element in the list value with 1...n being the remianing values // Types of error type is "start", "end", "target1", "target2", ..., "targetm" var curr_list = [] let i = 0 for (const key in this.points_json.error) { const error_type = this.points_json.error[key][0] const line = colors[i % colors.length].line let description if (error_type === "start") { description = "Initial program" } else if (error_type === "end") { description = "Most accurate alternative" } else { description = "Developer Target " + error_type.slice("target".length) } curr_list.push( Element("label", [ Element("input", { type: "checkbox", style: "accent-color: " + line.stroke, checked: selected_functions.includes(error_type), onclick: (e) => this.render(selected_var_name, toggle(error_type, selected_functions)) }, []), Element("span", { className: "functionDescription" }, [ " ", description]), ])) i += 1 } $elt.replaceChildren.apply( $elt, curr_list, ); }, sliding_window: function(A, size) { const half = Math.floor(size / 2) const running_sum = A.reduce((acc, v) => (acc.length > 0 ? acc.push(v.y + acc[acc.length - 1]) : acc.push(v.y), acc), []) return running_sum.reduce((acc, v, i) => { const length = (i - half) < 0 ? half + i : (i + half) >= running_sum.length ? (running_sum.length - (i - half)) : size const top = (i + half) >= running_sum.length ? running_sum[running_sum.length - 1] : running_sum[i + half] const bottom = (i - half) < 0 ? 0 : running_sum[i - half] acc.push({average: (top - bottom) / length, x: A[i].x, length}) return acc }, []) }, plot: async function(varName, function_names) { const functionMap = new Map() for (const key in this.points_json.error) { if (this.points_json.error[key][1] !== false) { // Error type -> Actual Error points functionMap.set(this.points_json.error[key][0], this.points_json.error[key].slice(1)) } } const index = this.all_vars.indexOf(varName) // NOTE ticks and splitpoints include all vars, so we must index const { bits, points, error, ticks_by_varidx, splitpoints_by_varidx } = this.points_json const ticks = ticks_by_varidx[index] if (!ticks) { return Element("div", "The function could not be plotted on the given range for this input.") } const tick_strings = ticks.map(t => t[0]) const tick_ordinals = ticks.map(t => t[1]) const tick_0_index = tick_strings.indexOf("0") const grouped_data = points.map((p, i) => ({ input: p, error: Object.fromEntries(function_names.map(name => ([name, functionMap.get(name)[i]]))) })) const domain = [Math.min(...tick_ordinals), Math.max(...tick_ordinals)] let splitpoints = splitpoints_by_varidx[index].map(p => { return Plot.ruleX([p], { stroke: "#888" }); }); if (tick_strings.includes("0")) { splitpoints.push(Plot.ruleX([ tick_ordinals[tick_strings.indexOf("0")] ], { stroke: "#888" })); } let marks = [] let i = 0 for (let [name, _] of functionMap) { const line = colors[i % colors.length].line const dot = colors[i % colors.length].dot i += 1 const key_fn = fn => (a, b) => fn(a) - fn(b) const index = this.all_vars.indexOf(varName) const data = grouped_data.map(({ input, error }) => ({ x: input[index], y: 1 - error[name] / bits })).sort(key_fn(d => d.x)) .map(({ x, y }, i) => ({ x, y, i })) const compress = (L, out_len, chunk_compressor = points => points[0]) => L.reduce((acc, pt, i) => i % Math.floor(L.length / out_len) == 0 ? (acc.push(chunk_compressor(L.slice(i, i + Math.floor(L.length / out_len)))), acc) : acc, []) const bin_size = 128 const sliding_window_data = compress( this.sliding_window(data, bin_size), 800, points => ({ average: points.reduce((acc, e) => e.average + acc, 0) / points.length, x: points.reduce((acc, e) => e.x + acc, 0) / points.length })) marks = marks.concat([ Plot.dot(compress(data, 800), { x: "x", y: "y", r: 1.3, title: d => `x: ${d.x} \n i: ${d.i} \n bits of error: ${d.y}`, ...dot }), Plot.line(sliding_window_data, { x: "x", y: "average", strokeWidth: 2, ...line, }), ]); } const out = Plot.plot({ width: '800', height: '300', marks: splitpoints.concat(marks), x: { tickFormat: d => tick_strings[tick_ordinals.indexOf(d)], ticks: tick_ordinals, label: varName, line: true, grid: true, domain, }, y: { line: true, domain: [0, 1], tickFormat: "%",}, marginBottom: 0, marginRight: 0, }); out.setAttribute('viewBox', '0 0 820 320') return out }, render: async function(selected_var_name, selected_functions) { this.render_variables(this.$variables, selected_var_name, selected_functions); this.render_functions(this.$functions, selected_var_name, selected_functions); let $svg = this.elt.querySelector("svg"); this.elt.replaceChild(await this.plot(selected_var_name, selected_functions), $svg); } }) const CostAccuracy = new Component('#cost-accuracy', { setup: async function() { const $svg = this.elt.querySelector("svg"); const $tbody = this.elt.querySelector("tbody"); let response = await fetch("../results.json", { headers: {"content-type": "text/plain"}, method: "GET", mode: "cors", }); let results_json = await response.json(); // find right test by iterating through results_json for (let test of results_json.tests) { if (test.name == this.elt.dataset.benchmarkName) { let [initial_pt, best_pt, rest_pts] = test["cost-accuracy"]; let target_pts = test["target"] rest_pts = [best_pt].concat(rest_pts) $svg.replaceWith(await this.plot(test, initial_pt, target_pts, rest_pts)); $tbody.replaceWith(await this.tbody(test, initial_pt, target_pts, rest_pts)); break; } } }, plot: async function(benchmark, initial_pt, target_pts, rest_pts) { const bits = benchmark["bits"]; // The line differs from rest_pts in two ways: // - We filter to the actual pareto frontier, in case points moved // - We make a broken line to show the real Pareto frontier let line = [] let last = null; rest_pts.forEach((pt, i) => { let target = "alternative" + (i + 1); if (!document.getElementById(target)) return; if (!last || pt[1] > last[1]) { if (last) line.push([pt[0], last[1]]); line.push([pt[0], pt[1]]); last = pt; } }) const out = Plot.plot({ marks: [ Plot.line(line, { x: d => initial_pt[0]/d[0], y: d => 1 - d[1]/bits, stroke: "#00a", strokeWidth: 1, strokeOpacity: .2, }), Plot.dot(rest_pts, { x: d => initial_pt[0]/d[0], y: d => 1 - d[1]/bits, fill: "#00a", r: 3, }), Plot.dot([initial_pt], { x: d => initial_pt[0]/d[0], y: d => 1 - d[1]/bits, stroke: "#d00", symbol: "square", strokeWidth: 2 }), target_pts && Plot.dot(target_pts, { x: d => initial_pt[0]/d[0], y: d => 1 - d[1]/bits, stroke: "#080", symbol: "circle", strokeWidth: 2 }), ].filter(x=>x), marginBottom: 0, marginRight: 0, width: '400', height: '200', x: { line: true, nice: true, tickFormat: c => c + "×" }, y: { nice: true, line: true, domain: [0, 1], tickFormat: "%" }, }) out.setAttribute('viewBox', '0 0 420 220') return out }, tbody: async function(benchmark, initial_pt, target_pts, rest_pts) { const bits = benchmark["bits"]; const initial_accuracy = 100*(1 - initial_pt[1]/bits); let alt_number = 0; return Element("tbody", [ Element("tr", [ Element("th", "Initial program"), Element("td", initial_accuracy.toFixed(1) + "%"), Element("td", "1.0×") ]), rest_pts.map((d, i) => { let target = "alternative" + (i + 1); if (!document.getElementById(target)) return; let accuracy = 100*(1 - d[1]/bits); let speedup = initial_pt[0]/d[0]; alt_number++; return Element("tr", [ Element("th", [ Element("a", { href: "#" + target}, "Alternative " + alt_number) ]), Element("td", { className: accuracy >= initial_accuracy ? "better" : "" }, accuracy.toFixed(1) + "%"), Element("td", { className: speedup >= 1 ? "better" : "" }, speedup.toFixed(1) + "×") ])}), target_pts && target_pts.map((d, i) => { let accuracy = 100*(1 - d[1]/bits); let speedup = initial_pt[0]/d[0]; return Element("tr", [ Element("th", Element("a", { href: "#target" + (i + 1)}, "Developer Target " + (i + 1))), Element("td", { className: accuracy >= initial_accuracy ? "better" : "" }, accuracy.toFixed(1) + "%"), Element("td", { className: speedup >= 1 ? "better" : "" }, speedup.toFixed(1) + "×") ])}), ]); } }); var RenderMath = new Component(".math", { depends: function() { if (typeof window.renderMathInElement === "undefined") throw "KaTeX unavailable"; }, setup: function() { renderMathInElement(this.elt); }, }); var Timeline = new Component(".timeline", { setup: function() { var ts = this.elt.querySelectorAll(".timeline-phase"); for (var i = 0; i < ts.length; i++) { var timespan = +ts[i].getAttribute("data-timespan"); var type = ts[i].getAttribute("data-type"); ts[i].style.flexGrow = timespan; ts[i].title = type + " (" + Math.round(timespan/100)/10 + "s)"; } } }); var Bogosity = new Component(".bogosity", { setup: function() { var ts = this.elt.children; for (var i = 0; i < ts.length; i++) { var timespan = +ts[i].getAttribute("data-timespan"); ts[i].style.flexGrow = timespan; } } }); var Implementations = new Component(".programs", { setup: function() { this.dropdown = this.elt.querySelector("select"); this.programs = this.elt.querySelectorAll(".implementation"); this.elt.addEventListener("change", this.change); this.change(); }, change: function() { var lang = this.dropdown.options[this.dropdown.selectedIndex].text; for (var i = 0; i < this.programs.length; i++) { var $prog = this.programs[i]; if ($prog.dataset["language"] == lang) { $prog.style.display = "block"; } else { $prog.style.display = "none"; } } }, }); function pct(val, base) { return Math.floor(val/base * 10000) / 100 + "%"; } function time(s) { return Math.floor(s / 1000 * 100) / 100 + "s"; } function path(p) { if (!p) { return "???"; } else if (p[0] == "/") { var r = p.substr(p.toLowerCase().indexOf("/racket") + 1); var ds = r.split("/"); if (ds[1] == "share" && ds[2] == "pkgs") { return "/" + ds.slice(3).join("/"); } else if (ds[1] == "collects") { return "/" + ds.slice(2).join("/"); } else { return "/" + ds.join("/"); } } else { return p; } } var Profile = new Component("#profile", { setup: function() { var text = this.elt.querySelector(".load-text"); fetch("profile.json") .then(response => response.json()) .catch(function(error) { text.textContent = "Error loading profile data" }) .then(data => this.render(data)) }, render: function(json) { this.json = json; this.search = Element("input", { placeholder: "Search for a function...", autocomplete: "off", name: "profilefn", }, []); this.search.setAttribute("list", "profilefns"); var form = Element("form", { method: "GET", action: "" }, [ this.search, Element("datalist", { id: "profilefns" }, [ json.nodes.map(n => n.id && Element("option", n.id)) ]), ]); form.addEventListener("submit", this.doSearch); this.elt.appendChild(form); this.elt.appendChild(this.mkNode(json.nodes[json.nodes[0].callees[0].callee])); this.elt.classList.add("loaded"); }, mkNode: function(node) { var that = this; var nelt = Element("div", { className: "node" }, [ Element("a", { className: "name delete" }, node.id || "???"), Element("span", { className: "path" }, path(node.src)), Element("span", { className: "pct", title: "Self-time: " + pct(node.self, that.json.cpu_time) }, [ time(node.total), ]), ]); var elt = Element("div", { className: "profile-row" }, [ node.callers.sort((e1, e2) => e1.caller_time - e2.caller_time).map(function(edge) { var other = that.json.nodes[edge.caller]; elt = Element("div", { className: "edge" }, [ Element("a", { className: "name" }, other.id || "???"), Element("span", { className: "path" }, path(other.src)), Element("span", { className: "pct" }, pct(edge.caller_time, node.total)), ]); elt.children[0].addEventListener("click", that.addElt(other)); return elt; }), nelt, node.callees.sort((e1, e2) => e2.callee_time - e1.callee_time).map(function(edge) { var other = that.json.nodes[edge.callee]; elt = Element("div", { className: "edge" }, [ Element("a", { className: "name" }, other.id || "???"), Element("span", { className: "path" }, path(other.src)), Element("span", { className: "pct" }, pct(edge.callee_time, node.total)), ]); elt.children[0].addEventListener("click", that.addElt(other)); return elt; }), ]); nelt.children[0].addEventListener("click", function() { elt.remove(); }); return elt; }, addElt: function(other) { var that = this; return function() { var newelt = that.mkNode(other) that.elt.appendChild(newelt); newelt.scrollTo(); return newelt; } }, doSearch: function(e) { e.preventDefault(); var term = this.search.value; var elt = this.addElt(this.json.nodes.find(n => n.id == term))(); elt.scrollTo(); this.search.value = ""; return false; } }) function makelabel(i, base, factor) { var num = i; var den = 1; if (base > 0) num *= Math.pow(10, base); else if (base < 0) den *= Math.pow(10, -base); if (factor > 0) num *= Math.pow(2, factor); if (factor < 0) den *= Math.pow(2, -factor); return num / den; } function histogram(id, xdata, ydata, options) { var width = options?.width ?? 676; var height = options?.height ?? 60; var margin = 5; var labels = 10; var ticks = 5; var bucketnum = options?.buckets ?? 25; var bucketwidth = Math.round(width / bucketnum); var canvas = document.getElementById(id); if (xdata.length == 0 || (ydata && xdata.length != ydata.length)) { return canvas.remove(); } canvas.setAttribute("width", margin + width + margin + "px"); canvas.setAttribute("height", labels + margin + height + ticks + margin + labels + "px"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.strokeStyle = "black"; ctx.moveTo(margin, labels + margin + height); ctx.lineTo(margin + width, labels + margin + height); ctx.stroke(); var xma = options?.max ?? Math.max.apply(null, xdata); var buckets = Array(bucketnum); var sum = 0; buckets.fill(0); for (var i = 0; i < xdata.length; i++) { var j = Math.floor(xdata[i] / xma * buckets.length); var weight = ydata ? ydata[i] : xdata[i]; buckets[Math.min(j, buckets.length-1)] += weight; sum += weight; } var yma = Math.max.apply(null, buckets); ctx.fillStyle = "rgba(0, 0, 0, .2)"; for (var i = 0; i < buckets.length; i++) { ctx.fillRect(margin + i/buckets.length*width, labels + margin + height, width/buckets.length, -height*buckets[i]/yma); } ctx.fillStyle = "black"; ctx.textBaseline = "bottom"; ctx.textAlign = "center"; for (var i = 0; i < buckets.length; i++) { if (buckets[i] == 0) continue; ctx.fillText(Math.round(buckets[i] / sum * 100) + "%", margin + (i + .5)/buckets.length * width, labels + height*(1 - buckets[i]/yma)); } ctx.textBaseline = "top"; var base = Math.round(Math.log10(xma)) - 1 var step = Math.pow(10, base); var factor; if (xma / step > 20) factor = +1; else if (xma / step < 10) factor = -1; else factor = 0; step *= Math.pow(2, factor); for (var i = 0; i < 10 * Math.sqrt(10); i++) { var pos = i * step; if (pos > xma) break; ctx.beginPath(); ctx.moveTo(pos / xma * width + margin, labels + margin + height); ctx.lineTo(pos / xma * width + margin, labels + margin + height + ticks); var label = makelabel(i, base, factor); ctx.fillText(label, pos / xma * width + margin, labels + margin + height + ticks + margin); ctx.stroke(); } } function run_components() { for (var i = 0; i < window.COMPONENTS.length; i++) { var component = window.COMPONENTS[i]; var elts = document.querySelectorAll(component.selector); try { if (elts.length > 0 && component.fns.depends) component.fns.depends(); } catch (e) { console.error(e); continue; } for (var j = 0; j < elts.length; j++) { var instance = new ComponentInstance(elts[j], component); console.log("Initiating", component.selector, "component at", elts[j]); try { instance.setup(); } catch (e) { console.error(e); } } } } window.addEventListener("load", run_components); ================================================ FILE: src/reports/timeline.rkt ================================================ #lang racket (require json (only-in xml write-xexpr xexpr?) racket/date) (require "../utils/common.rkt" "data.rkt" "common.rkt" "../syntax/platform.rkt" "../syntax/types.rkt" "../syntax/float.rkt" "../config.rkt" "../syntax/batch.rkt") (provide make-timeline) (define timeline-phase? (hash/c symbol? any/c)) (define timeline? (listof timeline-phase?)) ;; This first part handles timelines for a single Herbie run (define (make-timeline name timeline #:info [info #f] #:path [path "."]) `(html (head (meta ([charset "utf-8"])) (title "Metrics for " ,(~a name)) (link ([rel "stylesheet"] [type "text/css"] [href ,(if info "report.css" "../report.css")])) (script ([src ,(if info "report.js" "../report.js")]))) (body ,(render-menu (~a name) #:path path (if info `(("Report" . "index.html")) `(("Details" . "graph.html")))) ,(if info (render-about info) "") ,(render-timeline timeline) ,(render-profile)))) (define/contract (render-timeline timeline) (-> timeline? xexpr?) (define time (apply + (map (curryr dict-ref 'time) timeline))) `(section ([id "process-info"]) (p ((class "header")) "Time bar (total: " (span ((class "number")) ,(format-time time)) ")") (div ((class "timeline")) ,@(for/list ([n (in-naturals)] [curr timeline]) `(div ((class ,(format "timeline-phase timeline-~a" (dict-ref curr 'type))) [data-id ,(format "timeline~a" n)] [data-type ,(~a (dict-ref curr 'type))] [data-timespan ,(~a (dict-ref curr 'time))])))) ,@(for/list ([phase timeline] [n (in-naturals)]) (render-phase phase n time)))) (define/contract (render-phase curr n total-time) (-> timeline-phase? integer? real? xexpr?) (define time (dict-ref curr 'time)) (define type (dict-ref curr 'type)) `(div ((class ,(format "timeline-block timeline-~a" type)) [id ,(format "timeline~a" n)]) (h3 (a ([href ,(format "#timeline~a" n)]) ,(~a type)) (span ((class "time")) ,(format-time time) " (" ,(format-percent time total-time) ")")) (dl ,@(dict-call curr render-phase-memory 'memory 'gc-time) ,@(dict-call curr render-phase-algorithm 'method) ,@(dict-call curr render-phase-accuracy 'accuracy 'oracle 'baseline) ,@(dict-call curr render-phase-pruning 'kept) ,@(dict-call curr render-phase-error 'min-error) ,@(dict-call curr render-phase-egraph 'egraph) ,@(dict-call curr render-phase-stop 'stop) ,@(dict-call curr render-phase-counts 'count) ,@(dict-call curr render-phase-alts/shared 'alts 'batch) ,@(dict-call curr render-phase-inputs 'inputs 'outputs) ,@(dict-call curr render-phase-times 'times) ,@(dict-call curr render-phase-series 'series) ,@(dict-call curr render-phase-bstep 'bstep) ,@(dict-call curr render-phase-branches 'branch 'batch) ,@(dict-call curr render-phase-sampling 'sampling) ,@(dict-call curr (curryr simple-render-phase "Symmetry") 'symmetry) ,@(dict-call curr render-phase-outcomes 'outcomes) ,@(dict-call curr render-phase-compiler 'compiler) ,@(dict-call curr render-phase-mixed-sampling 'mixsample) ,@(dict-call curr render-phase-bogosity 'bogosity) ,@(dict-call curr render-phase-allocations 'allocations)))) (define/reset id-counter 0) (define (make-id) (id-counter (+ 1 (id-counter))) (id-counter)) (define (dict-call d f . args) (if (andmap (curry dict-has-key? d) args) (apply f (map (curry dict-ref d) args)) '())) (define (render-phase-algorithm algorithm) `((dt "Algorithm") (dd (table ((class "times")) ,@(for/list ([alg (in-list (sort (group-by identity algorithm) > #:key length))]) `(tr (td ,(~r (length alg) #:group-sep " ") "×") (td ,(~a (car alg))))))))) (define (render-phase-bogosity bogosity) (match-define (list domain-info) bogosity) (define total (round (apply + (hash-values domain-info)))) (define tags '(valid unknown infinite unsamplable invalid precondition)) `((dt "Bogosity") (dd (div ((class "bogosity")) ,@(for/list ([tag tags]) `(div ((class ,(format "bogosity-~a" tag)) [data-id ,(format "bogosity-~a" tag)] [data-type ,(~a tag)] [data-timespan ,(~a (hash-ref domain-info tag 0))] [title ,(format "~a (~a)" tag (format-percent (hash-ref domain-info tag 0) total))]))))))) (define (format-value v) (cond [(real? v) (~a v)] [(equal? (hash-ref v 'type) "real") (hash-ref v 'value)] [else (define repr-name (hash-ref v 'type)) (define repr (get-representation (read (open-input-string repr-name)))) (value->string ((representation-ordinal->repr repr) (string->number (hash-ref v 'ordinal))) repr)])) (define (render-phase-bstep iters) `((dt "Steps") (dd (table (tr (th "Time") (th "Left") (th "Right")) ,@(for/list ([rec (in-list iters)]) (match-define (list time v1 v2) rec) `(tr (td ,(format-time time)) (td (pre ,(format-value v1))) (td (pre ,(format-value v2))))))))) (define (render-phase-egraph iters) (define costs (map third iters)) (define last-useful-iter (last (filter (compose (curry = (apply min costs)) third) iters))) `((dt "Iterations") (dd (p "Useful iterations: " ,(~a (first last-useful-iter)) " (" ,(format-time (fourth last-useful-iter)) ")") (table ((class "times")) (tr (th "Iter") (th "Nodes") (th "Cost")) ,@(for/list ([rec (in-list (reverse iters))]) (match-define (list iter nodes cost t) rec) `(tr (td ,(~a iter)) (td ,(~a nodes)) (td ,(~a cost)))))))) (define (render-phase-stop data) (match-define (list (list reasons counts) ...) (sort data > #:key second)) `((dt "Stop Event") (dd (table ((class "times")) ,@(for/list ([reason reasons] [count counts]) `(tr (td ,(~r count #:group-sep " ") "×") (td ,(~a reason)))))))) (define (average . values) (/ (apply + values) (length values))) (define (render-phase-mixed-sampling mixsample) (define total-time (apply + (map first mixsample))) (define (format-memory-bytes bytes) (format "~a MiB" (~r (/ bytes (expt 2 20)) #:precision '(= 1)))) `((dt "Precisions") (dd (details (summary "Click to see histograms. Total time spent on operations: " ,(format-time total-time)) ,@(map first (sort (for/list ([rec (in-list (group-by second mixsample))]) ; group by operator ; rec = '('(time op precision) ... '(time op precision)) (define n (random 100000)) (define op (second (car rec))) (define precisions (map third rec)) (define times (map first rec)) (define memories (map fourth rec)) (define time-per-op (round (apply + times))) (define memory-per-op (apply + memories)) (list `(details (summary (code ,op) ": " ,(format-time time-per-op) " (" ,(format-percent time-per-op total-time) " of total, " ,(format-memory-bytes memory-per-op) ")") (canvas ([id ,(format "calls-~a" n)] [title "Histogram of precisions of the used operation"])) (script "histogram(\"" ,(format "calls-~a" n) "\", " ,(jsexpr->string precisions) ", " ,(jsexpr->string times) ", " "{\"max\" : " ,(~a (*max-mpfr-prec*)) "})")) time-per-op)) > #:key second)))))) (define (render-phase-sampling sampling) (define total (round (apply + (hash-values (cadr (car sampling)))))) (define fields '(("Valid" . valid) ("Unknown" . unknown) ("Precondition" . precondition) ("Infinite" . infinite) ("Domain" . invalid) ("Can't" . unsamplable))) `((dt "Search") (dd (table ((class "times")) (tr (th "Probability") ,@(for/list ([(name sym) (in-dict fields)]) `(th ,name)) (th "Iter")) ,@(for/list ([(n table) (in-dict (sort sampling < #:key first))]) `(tr (td ,(format-percent (hash-ref (car table) 'valid 0) (+ (hash-ref (car table) 'valid 0) (hash-ref (car table) 'unknown 0)))) ,@(for/list ([(name sym) (in-dict fields)]) `(td ,(format-percent (hash-ref (car table) sym 0) total))) (td ,(~a n)))))))) (define (simple-render-phase info name) (if (positive? (length (first info))) `((dt ,name) (dd ,@(map (lambda (s) `(p ,(~a s))) (first info)))) empty)) (define (render-phase-accuracy accuracy oracle baseline) (define rows (sort (for/list ([acc accuracy] [ora oracle] [bas baseline]) (list (- acc ora) (- bas acc))) > #:key first)) (define bits (map first rows)) (define total-remaining (apply + accuracy)) `((dt "Accuracy") (dd (p "Total " ,(format-bits (apply + bits) #:unit #t) " remaining" " (" ,(format-percent (apply + bits) total-remaining) ")" (p "Threshold costs " ,(format-bits (apply + (filter (curry > 1) bits))) "b" " (" ,(format-percent (apply + (filter (curry > 1) bits)) total-remaining) ")") ,@(if (> (length rows) 1) `((table ((class "times")) ,@(for/list ([rec (in-list rows)] [_ (in-range 5)]) (match-define (list left gained) rec) `(tr (td ,(format-bits left #:unit #t)) (td ,(format-percent gained (+ left gained))))))) '()))))) (define (render-phase-pruning kept-data) (match-define (list kept) kept-data) (define (altnum kind [col #f]) (define rec (hash-ref kept kind)) (match col [#f (first rec)] [0 (- (first rec) (second rec))] [1 (second rec)])) (define kept-alts (+ (altnum 'new 1) (altnum 'fresh 1))) (define done-alts (+ (altnum 'done 1) (altnum 'picked 1))) `((dt "Pruning") (dd (p ,(~a (+ kept-alts done-alts)) " alts after pruning (" ,(~a kept-alts) " fresh and " ,(~a done-alts) " done)") (table ((class "states")) (thead (tr (th) (th "Pruned") (th "Kept") (th "Total"))) (tbody ,@(for/list ([type '(new fresh picked done)]) `(tr (th ,(string-titlecase (~a type))) (td ,(~r (altnum type 0) #:group-sep " ")) (td ,(~r (altnum type 1) #:group-sep " ")) (td ,(~r (altnum type) #:group-sep " "))))) (tfoot (tr (th "Total") (td ,(~r (apply + (map (curryr altnum 0) '(new fresh picked done))) #:group-sep " ")) (td ,(~r (apply + (map (curryr altnum 1) '(new fresh picked done))) #:group-sep " ")) (td ,(~r (apply + (map altnum '(new fresh picked done))) #:group-sep " ")))))))) (define (render-phase-memory mem gc-time) (match-define (list live alloc) (car mem)) `((dt "Memory") (dd ,(~r (/ live (expt 2 20)) #:group-sep " " #:precision '(= 1)) "MiB live, " ,(~r (/ alloc (expt 2 20)) #:group-sep " " #:precision '(= 1)) "MiB allocated; " ,(format-time gc-time) " collecting garbage"))) (define (render-phase-error min-error-table) (match-define (list min-error repr-name) (car min-error-table)) (define repr (get-representation (read (open-input-string repr-name)))) `((dt "Accuracy") (dd ,(format-accuracy min-error repr #:unit "%") ""))) (define (render-phase-counts alts) `((dt "Counts") ,@(for/list ([rec (in-list alts)]) (match-define (list inputs outputs) rec) `(dd ,(~r inputs #:group-sep " ") " → " ,(~r outputs #:group-sep " "))))) (define (render-phase-alts/shared alts shared-batch) (match-define (list (? hash? shared-batch*)) shared-batch) (define nodes (hash-ref shared-batch* 'nodes)) (define (single-root-jsexpr root) (hash 'nodes nodes 'roots (list root))) `((dt "Alt Table") (dd (details (summary "Click to see full alt table") (table ((class "times")) (thead (tr (th "Status") (th "Accuracy") (th "Program"))) ,@ (for/list ([rec (in-list alts)]) (match-define (list root status score repr-name) rec) (define repr (get-representation (read (open-input-string repr-name)))) `(tr ,(match status ["next" `(td (span ([title "Selected for next iteration"]) "▶"))] ["done" `(td (span ([title "Selected in a prior iteration"]) "✓"))] ["fresh" `(td)]) (td ,(format-accuracy score repr #:unit "%") "") (td (pre ,(jsexpr->batch-exprs (single-root-jsexpr root))))))))))) (define (render-phase-times times) (define hist-id (make-id)) `((dt "Calls") (dd (p ,(~r (length times) #:group-sep " ") " calls:") (canvas ([id ,(format "calls-~a" hist-id)] [title "Weighted histogram; height corresponds to percentage of runtime in that bucket."])) (script ,(format "histogram('calls-~a', " hist-id) ,(jsexpr->string (map first times)) ")") (table ((class "times")) ,@(for/list ([rec (in-list (sort times > #:key first))] [_ (in-range 5)]) (match-define (list time batch-jsexpr) rec) `(tr (td ,(format-time time)) (td (pre ,(jsexpr->batch-exprs batch-jsexpr))))))))) (define (render-phase-series times) (define hist-id (make-id)) `((dt "Calls") (dd (p ,(~a (length times)) " calls:") (canvas ([id ,(format "calls-~a" hist-id)] [title "Weighted histogram; height corresponds to percentage of runtime in that bucket."])) (script ,(format "histogram('calls-~a', " hist-id) ,(jsexpr->string (map first times)) ")") (table ((class "times")) (thead (tr (th "Time") (th "Variable") (th "Point"))) ,@(for/list ([rec (in-list (sort times > #:key first))] [_ (in-range 5)]) (match-define (list time var transform) rec) `(tr (td ,(format-time time)) (td (pre ,var)) (td ,transform))))))) (define (render-phase-compiler compiler) (match-define (list (list sizes compileds) ...) compiler) (define size (apply + sizes)) (define compiled (apply + compileds)) `((dt "Compiler") (dd (p "Compiled " ,(~r size #:group-sep " ") " to " ,(~r compiled #:group-sep " ") " computations " "(" ,(format-percent (- size compiled) size) " saved)")))) (define (render-phase-branches branches shared-batch) (match-define (list (? hash? shared-batch*)) shared-batch) (define (single-root-jsexpr root) (hash 'nodes (hash-ref shared-batch* 'nodes) 'roots (list root))) `((dt "Results") (dd (table ((class "times")) (thead (tr (th "Accuracy") (th "Segments") (th "Branch"))) ,@(for/list ([rec (in-list branches)]) (match-define (list branch-root score splits repr-name) rec) (define repr (get-representation (read (open-input-string repr-name)))) `(tr (td ,(format-accuracy score repr #:unit "%") "") (td ,(~a splits)) (td (pre ,(jsexpr->batch-exprs (single-root-jsexpr branch-root)))))))))) (define (render-phase-outcomes outcomes) `((dt "Samples") (dd (table ((class "times")) ,@(for/list ([rec (in-list (sort outcomes > #:key first))]) (match-define (list time precision category count) rec) `(tr (td ,(format-time time)) (td ,(~r count #:group-sep " ") "×") (td ,(~a precision)) (td ,(~a category)))))))) (define (batch-jsexpr? x) (and (hash? x) (hash-has-key? x 'nodes))) (define (jsexpr->exprs x) (if (batch-jsexpr? x) (jsexpr->batch-exprs x) (string-join (map ~a x) "\n"))) (define (render-phase-inputs inputs outputs) `((dt "Calls") (dd ,@(for/list ([input-jsexpr inputs] [output-jsexpr outputs] [n (in-naturals 1)]) `(details (summary "Call " ,(~a n)) (table (thead (tr (th "Inputs"))) (tr (td (pre ,(jsexpr->exprs input-jsexpr))))) (table (thead (tr (th "Outputs"))) (tr (td (pre ,(jsexpr->exprs output-jsexpr)))))))))) (define (render-phase-allocations allocations) (define sorted (sort allocations > #:key second)) (define total (apply + (map second sorted))) `((dt "Allocations") (dd (table ((class "times")) (thead (tr (th "Allocated") (th "Percent") (th "Phase"))) ,@(for/list ([rec (in-list sorted)]) (match-define (list type alloc) rec) `(tr (td ,(~r (/ alloc (expt 2 20)) #:group-sep " " #:precision '(= 1)) " MiB") (td ,(format-percent alloc total)) (td (code ,(~a type))))) (tfoot (tr (td ,(~r (/ total (expt 2 20)) #:group-sep " " #:precision '(= 1)) " MiB") (td ,(format-percent total total)) (td (code "total")))))))) ;; This next part handles summarizing several timelines into one details section for the report page. (define (render-about info) (match-define (report-info date commit branch seed flags points iterations tests) info) `(table ((id "about")) (tr (th "Date:") (td ,(date->string date))) (tr (th "Commit:") (td (abbr ([title ,commit]) ,(with-handlers ([exn:fail:contract? (const commit)]) (substring commit 0 8))) " on " ,branch)) (tr (th "Seed:") (td ,(~a seed))) (tr (th "Parameters:") (td ,(~a (*num-points*)) " points for " ,(~a (*num-iterations*)) " iterations")) (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 (in-list (changed-flags))]) (match-define (list delta class flag) rec) `(kbd ,(match delta ['enabled "+o"] ['disabled "-o"]) " " ,(~a class) ":" ,(~a flag))))))))) (define (render-profile) `(section ([id "profile"]) (h1 "Profiling") (p ((class "load-text")) "Loading profile data..."))) ================================================ FILE: src/reports/traceback.rkt ================================================ #lang racket (require (only-in xml write-xexpr xexpr?)) (require "../utils/common.rkt" "../syntax/read.rkt" "common.rkt") (provide make-traceback) (define (make-traceback result-hash) (match (hash-ref result-hash 'status) ["timeout" (render-timeout result-hash)] ["failure" (render-failure result-hash)] [status (error 'make-traceback "unexpected status ~a" status)])) (define (render-failure result-hash) (define test (car (load-tests (open-input-string (hash-ref result-hash 'test))))) (define warnings (hash-ref result-hash 'warnings)) (define backend (hash-ref result-hash 'backend)) ; unpack the exception (match-define (list 'exn type msg url extra traceback) backend) `(html (head (meta ((charset "utf-8"))) (title "Exception for " ,(~a (test-name test))) (link ((rel "stylesheet") (type "text/css") (href "../report.css"))) ,@js-tex-include (script ([src "../report.js"]))) (body ,(render-menu (~a (test-name test)) (list '("Report" . "../index.html") '("Metrics" . "timeline.html"))) ,(render-warnings warnings) ,(render-specification test) ,(if type `(section ([id "user-error"] (class "error")) (h2 ,(~a msg) " " (a ([href ,url]) "(more)"))) "") ,(if type "" `(,@(render-reproduction test #:bug? #t) (section ([id "backtrace"]) (h2 "Backtrace") ,(render-traceback msg traceback))))))) (define (render-loc loc) (match loc [(list file line col) `((td ,(~a file)) (td ,(~a line)) (td ,(~a col)))] [#f `((td ([colspan "3"]) "unknown"))])) (define (render-traceback msg traceback) `(table (thead (th ([colspan "2"]) ,msg) (th "L") (th "C")) (tbody ,@(for/list ([(name loc) (in-dict traceback)]) `(tr (td ((class "procedure")) ,(~a name)) ,@(render-loc loc)))))) (define (render-timeout result-hash) (define test (car (load-tests (open-input-string (hash-ref result-hash 'test))))) (define time (hash-ref result-hash 'time)) (define warnings (hash-ref result-hash 'warnings)) `(html (head (meta ((charset "utf-8"))) (title "Timeout for " ,(~a (test-name test))) (link ((rel "stylesheet") (type "text/css") (href "../report.css"))) ,@js-tex-include (script ([src "../report.js"]))) (body ,(render-menu (~a (test-name test)) (list '("Report" . "../index.html") '("Metrics" . "timeline.html"))) ,(render-warnings warnings) ,(render-specification test) (section ([id "user-error"] (class "error")) (h2 "Timeout after " ,(format-time time)) (p "Use the " (code "--timeout") " flag to change the timeout."))))) ================================================ FILE: src/syntax/batch.rkt ================================================ #lang racket (require "syntax.rkt" "../utils/common.rkt" "../utils/dvector.rkt") (provide progs->batch ; List -> (Batch, List) expr-recurse (struct-out batch) batch-empty ; Batch batch-push! batch-add! ; Batch -> (or Expr Batchref Expr) -> Batchref batch-copy-only! batch-length ; Batch -> Integer batch-tree-size ; Batch -> List -> Integer batch-free-vars ; Batch -> (Batchref -> Set) in-batch ; Batch -> Sequence batch-reachable ; Batch -> List -> (Node -> Boolean) -> List batch-exprs batch-recurse batch-get-nodes batch->jsexpr jsexpr->batch-exprs (struct-out batchref) deref) ; Batchref -> Expr ;; Batches store these recursive structures, flattened (struct batch ([nodes #:mutable] [index #:mutable])) (struct batchref (batch idx) #:transparent) ;; --------------------------------- CORE BATCH FUNCTION ------------------------------------ (define (batch-empty) (batch (make-dvector) (make-hash))) (define (in-batch batch [start 0] [end #f] [step 1]) (in-dvector (batch-nodes batch) start end step)) (define (batch-get-nodes b) (dvector->vector (batch-nodes b))) ;; This function defines the recursive structure of expressions (define (expr-recurse expr f) (match expr [(approx spec impl) (approx (f spec) (f impl))] [(hole precision spec) (hole precision (f spec))] [(list op) (list op)] [(list op arg1) (list op (f arg1))] [(list op arg1 arg2) (list op (f arg1) (f arg2))] [(list op arg1 arg2 arg3) (list op (f arg1) (f arg2) (f arg3))] [(list op args ...) (cons op (map f args))] [_ expr])) (define (batch-length b) (dvector-length (batch-nodes b))) (define (batch-push! b term) (define hashcons (batch-index b)) (batchref b (hash-ref! hashcons term (lambda () (dvector-add! (batch-nodes b) term))))) (define (batch-add! b expr) (define (munge prog) (match prog [(batchref b* idx*) (assert-batch-brf! b prog) idx*] [_ (batchref-idx (batch-push! b (expr-recurse prog munge)))])) (batchref b (munge expr))) (define (deref x) (match-define (batchref b idx) x) (expr-recurse (dvector-ref (batch-nodes b) idx) (lambda (ref) (batchref b ref)))) (define (progs->batch exprs #:vars [vars '()]) (define out (batch-empty)) (for ([var (in-list vars)]) (batch-push! out var)) (define brfs (for/list ([expr (in-list exprs)]) (batch-add! out expr))) (values out brfs)) ;; batch-recurse iterates only over its children ;; A lot of parts of Herbie rely on that (define (batch-recurse batch f) (define out (make-dvector (batch-length batch))) (define visited (make-dvector (batch-length batch) #f)) (λ (brf) (assert-batch-brf! batch brf) (let loop ([brf brf]) (define idx (batchref-idx brf)) (cond [(and (> (dvector-capacity visited) idx) (dvector-ref visited idx)) (dvector-ref out idx)] [else (define res (f brf loop)) (dvector-set! out idx res) (dvector-set! visited idx #t) res])))) (define (assert-batch-brf! batch . brfs) (unless (andmap (compose (curry equal? batch) batchref-batch) brfs) (error 'assert-batch-brf! "One of batchrefs does not belong to the provided batch"))) ;; Function returns indices of children nodes within a batch for given roots, ;; where a child node is a child of a root + meets a condition - (condition node) (define (batch-reachable batch brfs #:condition [condition (const #t)]) ; Little check (apply assert-batch-brf! batch brfs) (define len (batch-length batch)) (define child-mask (make-vector len #f)) (for ([brf (in-list brfs)]) (vector-set! child-mask (batchref-idx brf) #t)) (for ([i (in-range (sub1 len) -1 -1)] [node (in-batch batch (sub1 len) -1 -1)] [child (in-vector child-mask (sub1 len) -1 -1)] #:when child) (cond [(condition node) (expr-recurse node (λ (n) (vector-set! child-mask n #t)))] [else (vector-set! child-mask i #f)])) ; Return batchrefs of children nodes in ascending order (for/list ([child (in-vector child-mask)] [i (in-naturals)] #:when child) (batchref batch i))) ;; Function constructs a vector of expressions for the given nodes of a batch (define (batch-exprs batch) (batch-recurse batch (lambda (brf recurse) (expr-recurse (deref brf) recurse)))) ;; Function constructs a vector of expressions for the given nodes of a batch (define (batch-copy-only! batch batch*) (batch-recurse batch* (lambda (brf recurse) (batch-push! batch (expr-recurse (deref brf) (compose batchref-idx recurse)))))) (define (batch-free-vars batch) (batch-recurse batch (lambda (brf recurse) (define node (deref brf)) (cond [(symbol? node) (set node)] [(approx? node) (recurse (approx-impl node))] [else (define arg-free-vars (mutable-set)) (expr-recurse node (lambda (i) (set-union! arg-free-vars (recurse i)))) arg-free-vars])))) (define (batch-tree-size batch brfs) (define counts (batch-recurse batch (lambda (brf recurse) (define args (reap [sow] (expr-recurse (deref brf) sow))) (apply + 1 (map recurse args))))) (apply + (map counts brfs))) ;; Converts a batch + roots to a JSON-compatible structure ;; Returns: (hash 'nodes [...] 'roots [idx1 idx2 ...]) ;; Nodes are: atoms (symbols->strings, numbers) or [op-string idx1 idx2 ...] (define (batch->jsexpr b brfs) (define batch* (batch-empty)) (define copy-f (batch-copy-only! batch* b)) (define brfs* (map copy-f brfs)) (define nodes (for/list ([node (in-batch batch*)]) (match node [(? symbol?) (~a node)] [(? number?) (~a node)] [(approx spec impl) (list "approx" spec impl)] [(hole precision spec) (list "hole" (~a precision) spec)] [(list op args ...) (cons (~a op) args)] [_ (~a node)]))) (hash 'nodes nodes 'roots (map batchref-idx brfs*))) ;; Converts a jsexpr batch to a single SSA-style string with O(n) size (define (jsexpr->batch-exprs jsexpr) (define nodes (hash-ref jsexpr 'nodes)) (define roots (hash-ref jsexpr 'roots)) (define node-vec (list->vector nodes)) ;; Pass 0: mark only the part of the graph reachable from roots. (define reachable? (make-vector (vector-length node-vec) #f)) (let loop ([stack roots]) (cond [(null? stack) #t] [(vector-ref reachable? (car stack)) (loop (cdr stack))] [else (define idx (car stack)) (vector-set! reachable? idx #t) (match (vector-ref node-vec idx) [(list _ args ...) (loop (append args (cdr stack)))] [_ (loop (cdr stack))])])) ;; Pass 1: count references to each node (define ref-counts (make-vector (vector-length node-vec) 0)) (for ([root roots]) (vector-set! ref-counts root (+ 1 (vector-ref ref-counts root)))) (for ([i (in-naturals)] [node (in-vector node-vec)] [reachable (in-vector reachable?)] #:when reachable) (match node [(list _ args ...) (for ([arg (in-list args)]) (vector-set! ref-counts arg (+ 1 (vector-ref ref-counts arg))))] ;; Never dedup constants & variables [_ (vector-set! ref-counts i -inf.0)])) ;; Pass 2: build expressions, using %N for multiply-referenced nodes (define exprs (make-vector (vector-length node-vec) #f)) (for ([i (in-naturals)] [node (in-vector node-vec)] [reachable (in-vector reachable?)] #:when reachable) (vector-set! exprs i (match node [(list op args ...) (format "(~a ~a)" op (string-join (for/list ([arg (in-list args)]) (if (> (vector-ref ref-counts arg) 1) (format "%~a" arg) (vector-ref exprs arg)))))] [_ (~a node)]))) ;; Output: one line per multi-ref node, then root expressions (define bindings (for/list ([i (in-naturals)] [reachable (in-vector reachable?)] #:when reachable #:when (> (vector-ref ref-counts i) 1)) (format "%~a = ~a" i (vector-ref exprs i)))) (define return-exprs (for/list ([root roots]) (if (> (vector-ref ref-counts root) 1) (format "%~a" root) (vector-ref exprs root)))) (string-join (append bindings return-exprs) "\n")) ;; --------------------------------- TESTS --------------------------------------- ; Tests for progs->batch and batch-exprs (module+ test (require rackunit) (define (test-munge-unmunge expr) (define-values (batch brfs) (progs->batch (list expr))) (check-equal? (list expr) (map (batch-exprs batch) brfs))) (define (f64 x) (literal x 'binary64)) (test-munge-unmunge '(* 1/2 (+ (exp x) (neg (/ 1 (exp x)))))) (test-munge-unmunge '(+ 1 (neg (* 1/2 (+ (exp (/ (sin 3) (cos 3))) (/ 1 (exp (/ (sin 3) (cos 3))))))))) (test-munge-unmunge '(cbrt x)) (test-munge-unmunge (list 'x)) (test-munge-unmunge `(+.f64 (sin.f64 ,(approx '(* 1/2 (+ (exp x) (neg (/ 1 (exp x))))) '(+.f64 ,(f64 3) (*.f64 ,(f64 25) (sin.f64 ,(f64 6)))))) ,(f64 4)))) ; Tests for remove-zombie-nodes (module+ test (require rackunit) (define (zombie-test #:nodes nodes #:roots roots) (define in-batch (batch nodes (make-hash))) (define brfs (map (curry batchref in-batch) roots)) (define out-batch (batch-empty)) (define copy-f (batch-copy-only! out-batch in-batch)) (define brfs* (map copy-f brfs)) (check-equal? (map (batch-exprs out-batch) brfs*) (map (batch-exprs in-batch) brfs)) (batch-nodes out-batch)) (check-equal? (create-dvector 2 0 '(sqrt 1) '(pow 0 2)) (zombie-test #:nodes (create-dvector 0 1 '(sqrt 0) 2 '(pow 3 2)) #:roots (list 4))) (check-equal? (create-dvector 0 '(sqrt 0) '(exp 1)) (zombie-test #:nodes (create-dvector 0 6 '(pow 0 1) '(* 2 0) '(sqrt 0) '(exp 4)) #:roots (list 5))) (check-equal? (create-dvector 0 1/2 '(+ 0 1)) (zombie-test #:nodes (create-dvector 0 1/2 '(+ 0 1) '(* 2 0)) #:roots (list 2))) (check-equal? (create-dvector 1/2 '(exp 0) 0 (approx 1 2)) (zombie-test #:nodes (create-dvector 0 1/2 '(+ 0 1) '(* 2 0) '(exp 1) (approx 4 0)) #:roots (list 5))) (check-equal? (create-dvector 1/2 'x '(* 1 1) 2 (approx 2 3) '(pow 0 4)) (zombie-test #:nodes (create-dvector 'x 2 1/2 '(sqrt 1) '(cbrt 1) '(* 0 0) (approx 5 1) '(pow 2 6)) #:roots (list 7))) (check-equal? (create-dvector 1/2 'x '(* 1 1) 2 (approx 2 3) '(pow 0 4) '(sqrt 3)) (zombie-test #:nodes (create-dvector 'x 2 1/2 '(sqrt 1) '(cbrt 1) '(* 0 0) (approx 5 1) '(pow 2 6)) #:roots (list 7 3)))) ; Tests for batch->jsexpr and jsexpr->batch-exprs (module+ test (require rackunit) (define (test-json-tostring expr expected) (define-values (batch brfs) (progs->batch (list expr))) (define jsexpr (batch->jsexpr batch brfs)) (define str (jsexpr->batch-exprs jsexpr)) (check-equal? str expected)) ; No sharing - just the expression (test-json-tostring '(+ x y) "(+ x y)") ; Shared subexpressions get their own bindings (test-json-tostring '(* 1/2 (+ (exp x) (neg (/ 1 (exp x))))) "%2 = (exp x)\n(* 1/2 (+ %2 (neg (/ 1 %2))))") ; Shared constants/variables are inlined (test-json-tostring '(sqrt (+ (* x x) (* y y))) "(sqrt (+ (* x x) (* y y)))")) ================================================ FILE: src/syntax/float.rkt ================================================ #lang racket (require math/base math/bigfloat math/flonum) (require "../utils/common.rkt" "../syntax/types.rkt" "../utils/errors.rkt") (provide repr-ulps ulps->bits midpoint two-midpoints random-generate string value->json json->value real->repr repr->real) (define (repr-ulps repr) (match (representation-type repr) [`(array ,_ ,_) (define elem-repr (array-representation-elem repr)) (define elem-ulps (repr-ulps elem-repr)) (lambda (x y) (for/sum ([x1 (in-vector x)] [y1 (in-vector y)]) (elem-ulps x1 y1)))] ['bool (lambda (x y) (if (equal? x y) 1 2))] ['real (define ->ordinal (representation-repr->ordinal repr)) (define special? (representation-special-value? repr)) (define max-error (+ 1 (expt 2 (representation-total-bits repr)))) (define finite-ulps (if (eq? repr ) (lambda (x y) (+ 1 (abs (flonums-between x y)))) (lambda (x y) (+ 1 (abs (- (->ordinal y) (->ordinal x))))))) (lambda (x y) (if (or (special? x) (special? y)) max-error (finite-ulps x y)))])) ;; Returns the midpoint of the representation's ordinal values, ;; not the real-valued midpoint (define (midpoint p1 p2 repr) (match (representation-type repr) [`(array ,_ ,_) (define elem-repr (array-representation-elem repr)) (for/vector #:length (vector-length p1) ([x1 (in-vector p1)] [y1 (in-vector p2)]) (midpoint x1 y1 elem-repr))] ['bool (and p1 p2)] ['real ((representation-ordinal->repr repr) (floor (/ (+ ((representation-repr->ordinal repr) p1) ((representation-repr->ordinal repr) p2)) 2)))])) (define (repr-round repr dir point) ((representation-repr->bf repr) (parameterize ([bf-rounding-mode dir]) ((representation-bf->repr repr) point)))) (define (two-midpoints repr lo hi) ; Midpoint is taken in repr-space, but values are stored in bf (define <-ordinal (compose (representation-repr->bf repr) (representation-ordinal->repr repr))) (define ->ordinal (compose (representation-repr->ordinal repr) (representation-bf->repr repr))) (define lower (<-ordinal (floor (/ (+ (->ordinal hi) (->ordinal lo)) 2)))) (define higher (repr-round repr 'up (bfnext lower))) ; repr-next (and (bf>= lower lo) (bf<= higher hi) ; False if lo and hi were already close together (cons lower higher))) (define (ulps->bits x) (real->double-flonum (log x 2))) (define (random-generate repr) (match (representation-type repr) [`(array ,_ ,_) (define elem-repr (array-representation-elem repr)) (define len (array-representation-len repr)) (for/vector #:length len ([_ (in-range len)]) (random-generate elem-repr))] ['bool (zero? (random-integer 0 2))] ['real (define bits (sub1 (representation-total-bits repr))) ((representation-ordinal->repr repr) (random-integer (- (expt 2 bits)) (expt 2 bits)))])) (define (=/total x1 x2 repr) (define ->ordinal (representation-repr->ordinal repr)) (define special? (representation-special-value? repr)) (or (= (->ordinal x1) (->ordinal x2)) (and (special? x1) (special? x2)))) (define (ordinal (representation-repr->ordinal repr)) (cond [(special? x1) #f] [(special? x2) #t] [else (< (->ordinal x1) (->ordinal x2))])) (define (<=/total x1 x2 repr) (or (json x repr) (match x [(? vector?) (define elem-repr (array-representation-elem repr)) (for/list ([v (in-vector x)]) (value->json v elem-repr))] [(? real?) (match x [(? rational?) x] [(or -inf.0 -inf.f) (hash 'type "real" 'value "-inf")] [(or +inf.0 +inf.f) (hash 'type "real" 'value "+inf")] [(or +nan.0 +nan.f) (hash 'type "real" 'value "NaN")])] [_ (hash 'type (~a (representation-name repr)) 'ordinal (~a ((representation-repr->ordinal repr) x)))])) (define (json->value x repr) (match x [(? list?) (define elem-repr (array-representation-elem repr)) (for/vector ([v (in-list x)]) (json->value v elem-repr))] [(? real?) (exact->inexact x)] [(? hash?) (match (hash-ref x 'type) ["real" (match (hash-ref x 'value) ["-inf" -inf.0] ["+inf" +inf.0] ["NaN" +nan.0] [_ +nan.0])] [_ ((representation-ordinal->repr repr) (string->number (hash-ref x 'ordinal)))])])) (define (value->string n repr) ;; Prints a number with relatively few digits (define ->bf (representation-repr->bf repr)) (define <-bf (representation-bf->repr repr)) ;; Linear search because speed not an issue (let loop ([precision 16]) (cond [(> precision (*max-mpfr-prec*)) (warn 'value-to-string #:url "faq.html#value-to-string" "Could not uniquely print ~a" n) n] [else (parameterize ([bf-precision precision]) (define bf (->bf n)) (if (=/total n (<-bf bf) repr) (match (bigfloat->string bf) ["-inf.bf" "-inf.0"] ["+inf.bf" "+inf.0"] ["+nan.bf" "+nan.0"] [x x]) (loop (+ precision 4))))]))) ; 2^4 > 10 (define (real->repr x repr) (match (representation-type repr) [`(array ,_ ,_) (define elem-repr (array-representation-elem repr)) (for/vector ([v (in-vector x)]) (real->repr v elem-repr))] ['real (parameterize ([bf-precision (representation-total-bits repr)]) ((representation-bf->repr repr) (bf x)))] ['bool x])) (define (repr->real x repr) (match (representation-type repr) [`(array ,_ ,_) (define elem-repr (array-representation-elem repr)) (for/vector ([v (in-vector x)]) (repr->real v elem-repr))] ['real (bigfloat->real ((representation-repr->bf repr) x))] ['bool x])) ================================================ FILE: src/syntax/generators.rkt ================================================ #lang racket (require math/flonum math/bigfloat ffi/unsafe) (require "rival.rkt" "../config.rkt" "types.rkt" "batch.rkt") (provide from-rival from-ffi from-libm from-bigfloat define-generator (struct-out generator)) (struct generator (gen)) (define-syntax-rule (define-generator ((name args ...) spec ctx) body ...) (define (name args ...) (generator (lambda (spec ctx) body ...)))) ; ----------------------- RIVAL GENERATOR --------------------------- (define/reset caches '() (lambda () (for ([cache (caches)]) (hash-clear! cache)))) (define-generator ((from-rival #:cache? [cache? #t]) spec ctx) (define-values (batch brfs) (progs->batch (list spec))) (define compiler (make-real-compiler batch brfs (list ctx))) (define fail ((representation-bf->repr (context-repr ctx)) +nan.bf)) (define (compute . pt) (define-values (_ exs) (real-apply compiler (list->vector pt))) (if exs (first exs) fail)) (cond [cache? (define cache (make-hash)) (caches (cons cache (caches))) (lambda pt (hash-ref! cache pt (lambda () (apply compute pt))))] [else compute])) ; ----------------------- FFI GENERATOR ----------------------------- ;; Looks up a function `name` with type signature `itype -> ... -> otype` ;; in the given FFI library and returns the function or `#f` if it ;; cannot be found. ;; ``` ;; (make-ffi ( ... )) ;; ``` (define (make-ffi lib name itypes otype) ; Repr matching (define (repr->ffi repr) (match (representation-name repr) ['binary64 _double] ['binary32 _float] ['integer _int] [else (raise-syntax-error 'repr->type "unknown type" repr)])) (get-ffi-obj name lib (_cprocedure (map repr->ffi itypes) (repr->ffi otype)) (const #f))) (define-generator ((from-ffi lib name) spec ctx) (let ([itypes (context-var-reprs ctx)] [otype (context-repr ctx)]) (or (make-ffi lib name itypes otype) (error 'ffi-generator "Could not find FFI implementation of `~a ~a ~a`" otype name itypes)))) (define libm-lib (ffi-lib #f)) (define (from-libm name) (from-ffi libm-lib name)) ; ----------------------- BIGFLOAT GENERATOR ------------------------ (define (repr->bf x type) ((representation-repr->bf type) x)) (define (bf->repr x type) ((representation-bf->repr type) x)) (define-generator ((from-bigfloat name) spec ctx) (let* ([itypes (context-var-reprs ctx)] [otype (context-repr ctx)] [op (dynamic-require '(lib "math/bigfloat") name)] [working-precision (representation-total-bits otype)]) (lambda pt (define pt* (map repr->bf pt itypes)) (define bf-out (parameterize ([bf-precision working-precision]) (apply op pt*))) (bf->repr bf-out otype)))) ================================================ FILE: src/syntax/load-platform.rkt ================================================ #lang racket (require racket/runtime-path) (require "../config.rkt" "../utils/errors.rkt" "platform.rkt") (provide activate-platform!) (define-runtime-module-path herbie10-platform "../platforms/herbie10.rkt") (define-runtime-module-path herbie20-platform "../platforms/herbie20.rkt") (define-runtime-module-path c-platform "../platforms/c.rkt") (define-runtime-module-path c-windows-platform "../platforms/c-windows.rkt") (define-runtime-module-path racket-platform "../platforms/racket.rkt") (define-runtime-module-path math-platform "../platforms/math.rkt") (define-runtime-module-path rival-platform "../platforms/rival.rkt") (define default-platforms (hash "herbie10" herbie10-platform "herbie20" herbie20-platform "c" c-platform "c-windows" c-windows-platform "racket" racket-platform "math" math-platform "rival" rival-platform)) (define platforms (make-hash)) (define (activate-platform! name) (define path (hash-ref default-platforms name (string->path name))) (define platform (hash-ref! platforms name (lambda () (dynamic-require path 'platform)))) (unless platform (raise-herbie-error "unknown platform `~a`, found (~a)" name (string-join (map ~a (hash-keys platforms)) ", "))) (*platform-name* name) (*active-platform* platform)) ================================================ FILE: src/syntax/matcher.rkt ================================================ ;; Minimal pattern matcher/substituter for S-expressions #lang racket (provide pattern-match pattern-substitute) ;; Unions two bindings. Returns #f if they disagree. (define (merge-bindings binding1 binding2) (and binding1 binding2 (let/ec quit (for/fold ([binding binding1]) ([(k v) (in-dict binding2)]) (dict-update binding k (λ (x) (if (equal? x v) v (quit #f))) v))))) ;; Pattern matcher that returns a substitution or #f. ;; A substitution is an association list of symbols and expressions. (define (pattern-match pattern expr) (match* (pattern expr) [((? number?) _) (and (equal? pattern expr) '())] [((? symbol?) _) (list (cons pattern expr))] [((list phead prest ...) (list head rest ...)) (and (equal? phead head) (= (length prest) (length rest)) (for/fold ([bindings '()]) ([pat (in-list prest)] [term (in-list rest)]) (merge-bindings bindings (pattern-match pat term))))] [(_ _) #f])) (define (pattern-substitute pattern bindings) ; pattern binding -> expr (match pattern [(? number?) pattern] [(? symbol?) (dict-ref bindings pattern)] [(list phead pargs ...) (cons phead (map (curryr pattern-substitute bindings) pargs))])) ================================================ FILE: src/syntax/platform-language.rkt ================================================ #lang racket (require "platform.rkt" "syntax.rkt" "types.rkt" "generators.rkt" "../utils/errors.rkt" "../config.rkt" rival/eval/types) (provide define-representation define-operation define-operations fpcore-context if-impl if-cost (rename-out [platform-module-begin #%module-begin]) (except-out (all-from-out racket) #%module-begin) (all-from-out "platform.rkt") (all-from-out "generators.rkt") (all-from-out "types.rkt")) ;; Core error checking code (define (check-spec! name ctx spec) (match-define (context vars repr var-reprs) ctx) (define env (map cons vars (map representation-type var-reprs))) (define otype (representation-type repr)) (define (infer spec) (match spec [(? number?) 'real] [(? symbol? x) (dict-ref env x (lambda () (rival-type spec env)))] [(list 'array elems ...) (if (null? elems) #f (let ([elem-ty (infer (first elems))]) (and elem-ty (for/and ([elem (in-list (rest elems))]) (equal? elem-ty (infer elem))) `(array ,elem-ty ,(length elems)))))] [(list 'ref arr idx) (match (infer arr) [`(array ,elem-ty ,_) elem-ty] [_ #f])] [_ (rival-type spec env)])) (define spec-type (infer spec)) (match spec-type [(== otype) (void)] [#f (error name "expression ~a is ill-typed, expected `~a`" spec otype)] [actual-ty (error name "expression ~a has type `~a`, expected `~a`" spec actual-ty otype)])) (define (check-fpcore! name fpcore) (match (fpcore-parameterize fpcore) [`(! ,props ... (,op ,args ...)) (unless (even? (length props)) (error 'define-operation "~a: unmatched property in ~a" name fpcore)) (unless (symbol? op) (error 'define-operation "~a: expected symbol `~a`" name op)) (for ([arg (in-list args)] #:unless (or (symbol? arg) (number? arg))) (error 'define-operation "~a: expected terminal `~a`" name arg))] [`(,op ,args ...) (unless (symbol? op) (error 'define-operation "~a: expected symbol `~a`" name op)) (for ([arg (in-list args)] #:unless (or (symbol? arg) (number? arg))) (error 'define-operation "~a: expected terminal `~a`" name arg))] [(? symbol?) (void)] [_ (error 'define-operation "Invalid fpcore for ~a: ~a" name fpcore)])) (define (check-fl-proc! name ctx fl-proc spec) (define fl-proc* (match fl-proc [(? generator?) ((generator-gen fl-proc) spec ctx)] [(? procedure?) fl-proc])) (unless (procedure-arity-includes? fl-proc* (length (context-vars ctx)) #t) (error 'define-operation "Procedure `~a` accepts ~a arguments, but ~a is provided" name (procedure-arity fl-proc*) (length (context-vars ctx)))) fl-proc*) (define (check-cost! name cost) (match cost [(? number?) (values cost +)] [(? procedure?) (values 0 cost)] [_ (error 'define-operation "Invalid cost for ~a: ~a" name cost)])) ;; Functions for the core operations (define fpcore-context (make-parameter '_)) (define (fpcore-parameterize spec) (let loop ([ctx (fpcore-context)]) (match ctx ['_ spec] [(list arg ...) (map loop arg)] [_ ctx]))) (define/contract (create-operator-impl! name ctx #:spec spec #:impl fl-proc #:fpcore fpcore #:cost cost) (-> symbol? context? #:spec any/c #:impl (or/c procedure? generator?) #:fpcore any/c #:cost (or/c real? procedure?) operator-impl?) (check-spec! name ctx spec) (check-fpcore! name fpcore) (define fl-proc* (check-fl-proc! name ctx fl-proc spec)) (define-values (cost* aggregate*) (check-cost! name cost)) (operator-impl name ctx spec (fpcore-parameterize fpcore) fl-proc* cost* aggregate*)) (define (platform-register-representation! platform #:repr repr #:cost cost) (define reprs (platform-representations platform)) (define repr-costs (platform-representation-costs platform)) ; Duplicate check (when (hash-has-key? reprs (representation-name repr)) (raise-herbie-error "Duplicate representation ~a in platform ~a" (representation-name repr) (*platform-name*))) ; Update tables (hash-set! reprs (representation-name repr) repr) (hash-set! repr-costs (representation-name repr) cost)) (define (platform-register-implementation! platform impl) ; Reprs check (define reprs (platform-representations platform)) (define otype (context-repr (operator-impl-ctx impl))) (define itype (context-var-reprs (operator-impl-ctx impl))) (define impl-reprs (map representation-name (remove-duplicates (cons otype itype)))) (for ([repr-name (in-list impl-reprs)] #:unless (hash-has-key? reprs repr-name)) (raise-herbie-error "Platform ~a missing representation ~a for ~a implementation" (*platform-name*) repr-name (operator-impl-name impl))) ; Duplicate check (define impls (platform-implementations platform)) (when (hash-has-key? impls (operator-impl-name impl)) (raise-herbie-error "Impl ~a is already registered in platform ~a" (operator-impl-name impl) (*platform-name*))) ; Update table (hash-set! impls (operator-impl-name impl) impl)) ;; Macros for the core operations (begin-for-syntax (define (parse-keyword-fields stx fields-stx allowed-keywords op-name) (define (oops! why [sub-stx #f]) (raise-syntax-error op-name why stx sub-stx)) (define result-hash (make-hasheq)) (let loop ([fields fields-stx]) (syntax-case fields () [() result-hash] [(kw val rest ...) (keyword? (syntax-e #'kw)) (let ([kw-sym (string->symbol (keyword->string (syntax-e #'kw)))]) (unless (member kw-sym allowed-keywords) (oops! (format "unknown keyword ~a" (syntax-e #'kw)) #'kw)) (when (hash-has-key? result-hash kw-sym) (oops! (format "multiple ~a clauses" (syntax-e #'kw)) #'kw)) (hash-set! result-hash kw-sym #'val) (loop #'(rest ...)))] [(kw) (keyword? (syntax-e #'kw)) (oops! (format "expected value after keyword ~a" (syntax-e #'kw)) #'kw)] [_ (oops! "bad syntax" fields)])))) (define-syntax (make-operator-impl stx) (define (oops! why [sub-stx #f]) (raise-syntax-error 'make-operator-impl why stx sub-stx)) (syntax-case stx (:) [(_ (id [var : repr] ...) rtype . fields) (let ([op-name #'id] [vars (syntax->list #'(var ...))]) (unless (identifier? op-name) (oops! "expected identifier" op-name)) (for ([var (in-list vars)] #:unless (identifier? var)) (oops! "expected identifier" var)) (define keywords (parse-keyword-fields stx #'fields '(spec fpcore impl cost) op-name)) (unless (hash-has-key? keywords 'spec) (raise-syntax-error op-name "missing `#:spec` keyword" stx)) (unless (hash-has-key? keywords 'impl) (raise-syntax-error op-name "missing `#:impl` keyword" stx)) (unless (hash-has-key? keywords 'cost) (raise-syntax-error op-name "missing `#:cost` keyword" stx)) ;; Build argument list for create-operator-impl! ;; Quote spec and fpcore, leave impl and cost unquoted ;; Default fpcore to spec if not provided (with-syntax ([spec-val (hash-ref keywords 'spec)] [fpcore-val (hash-ref keywords 'fpcore (hash-ref keywords 'spec))] [impl-val (hash-ref keywords 'impl)] [cost-val (hash-ref keywords 'cost)]) #'(create-operator-impl! 'id (context '(var ...) rtype (list repr ...)) #:spec 'spec-val #:impl impl-val #:fpcore 'fpcore-val #:cost cost-val)))] [_ (oops! "bad syntax")])) (define platform-being-defined (make-parameter #f)) (define-syntax-rule (define-representation repr #:cost cost) (platform-register-representation! (platform-being-defined) #:repr repr #:cost cost)) (define-syntax-rule (define-operation (name [arg irepr] ...) orepr flags ...) (let ([impl (make-operator-impl (name [arg : irepr] ...) orepr flags ...)]) (platform-register-implementation! (platform-being-defined) impl))) ;; Language definition and syntactic sugar / helpers (define-syntax (platform-module-begin stx) (with-syntax ([local-platform (datum->syntax stx 'platform)]) (syntax-case stx () [(_ content ...) #'(#%module-begin (define local-platform (make-empty-platform)) (define old-platform-being-defined (platform-being-defined)) (platform-being-defined local-platform) content ... (platform-being-defined old-platform-being-defined) (provide local-platform) (module+ main (display-platform local-platform)) (module test racket/base ))]))) (define-syntax (define-operations stx) (syntax-case stx () [(_ ([arg irepr] ...) orepr #:fpcore fc [name flags ...] ...) #'(parameterize ([fpcore-context 'fc]) (define-operation (name [arg irepr] ...) orepr flags ...) ...)] [(_ ([arg irepr] ...) orepr [name flags ...] ...) #'(begin (define-operation (name [arg irepr] ...) orepr flags ...) ...)])) (define (if-impl c t f) (if c t f)) (define ((if-cost base) c t f) (+ base c (max t f))) ================================================ FILE: src/syntax/platform.rkt ================================================ #lang racket (require racket/runtime-path) (require "../utils/common.rkt" "../utils/errors.rkt" "../config.rkt" "matcher.rkt" "types.rkt" "syntax.rkt" "../syntax/float.rkt" "generators.rkt" "batch.rkt") ;;; Platforms describe a set of representations, operator, and constants ;;; Herbie should use during its improvement loop. Platforms are just ;;; a "type signature" - they provide no implementations of floating-point ;;; operations (see plugins). During runtime, platforms will verify if ;;; every listed feature is actually loaded by Herbie and will panic if ;;; implemenations are missing. Unlike plugins, only one platform may be ;;; active at any given time and platforms may be activated or deactivated. ;;; ;;; A small API is provided for platforms for querying the supported ;;; operators, operator implementations, and representation conversions. (struct platform (representations implementations representation-costs) #:name $platform #:constructor-name create-platform #:methods gen:custom-write [(define (write-proc p port mode) (fprintf port "#"))]) (provide *active-platform* get-representation impl-exists? impl-info prog->spec batch-to-spec! get-fpcore-impl (struct-out $platform) ;; Platform API ;; Operator sets (contract-out [platform-reprs (-> platform? (listof representation?))] [platform-impls (-> platform? (listof symbol?))] [platform-repr-cost (-> platform? any/c any/c)] [platform-node-cost-proc (-> platform? procedure?)] [platform-cost-proc (-> platform? procedure?)]) ; Platform creation make-empty-platform display-platform make-representation (all-from-out "generators.rkt")) ;; Active platform (define *active-platform* (make-parameter #f)) (define (make-empty-platform) (define reprs (make-hash)) (define repr-costs (make-hash)) (define impls (make-hash)) (create-platform reprs impls repr-costs)) ;; Returns the representation associated with `name` ;; attempts to generate the repr if not initially found (define (get-representation name) (define platform (*active-platform*)) (define reprs (platform-representations platform)) (or (hash-ref reprs name #f) (raise-herbie-error "Could not find support for ~a representation: ~a in a platform ~a" name (string-join (map ~s (hash-keys reprs)) ", ") (*platform-name*)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; LImpl -> LSpec ;; Translates an LImpl to a LSpec. (define (prog->spec expr) (match expr [(? literal?) (literal-value expr)] [(? symbol?) expr] [(approx spec _) spec] [`(if ,cond ,ift ,iff) `(if ,(prog->spec cond) ,(prog->spec ift) ,(prog->spec iff))] [`(,impl ,args ...) (define vars (impl-info impl 'vars)) (define spec (impl-info impl 'spec)) (define env (map cons vars (map prog->spec args))) (pattern-substitute spec env)])) (define (batch-to-spec! batch brfs) (define lower (batch-recurse batch (lambda (brf recurse) (define node (deref brf)) (match node [(? literal?) (batch-push! batch (literal-value node))] [(? number?) brf] [(? symbol?) brf] [(hole _ spec) (recurse spec)] [(approx spec _) (recurse spec)] [(list (? impl-exists? impl) args ...) (define vars (impl-info impl 'vars)) (define spec (impl-info impl 'spec)) (define env (map cons vars (map recurse args))) (batch-add! batch (pattern-substitute spec env))] [(list op args ...) (batch-push! batch (cons op (map (compose batchref-idx recurse) args)))])))) (map lower brfs)) ;; Expression predicates ;; (define (impl-exists? op) (define platform (*active-platform*)) (define impls (platform-implementations platform)) (hash-has-key? impls op)) ;; Looks up a property `field` of an real operator `op`. ;; Panics if the operator is not found. (define/contract (impl-info impl-name field) (-> symbol? (or/c 'vars 'itype 'otype 'spec 'fpcore 'fl 'cost 'aggregate) any/c) (define impls (platform-implementations (*active-platform*))) (define impl (hash-ref impls impl-name (lambda () (error 'impl-info "unknown impl '~a in platform ~a" impl-name (*platform-name*))))) (case field [(vars) (context-vars (operator-impl-ctx impl))] [(itype) (context-var-reprs (operator-impl-ctx impl))] [(otype) (context-repr (operator-impl-ctx impl))] [(spec) (operator-impl-spec impl)] [(fpcore) (operator-impl-fpcore impl)] [(fl) (operator-impl-fl impl)] [(cost) (operator-impl-cost impl)] [(aggregate) (operator-impl-aggregate impl)])) (define (platform-impls platform) (hash-keys (platform-implementations platform))) (define (platform-reprs platform) (hash-values (platform-representations platform))) ; Representation (terminal) cost in a platform. (define (platform-repr-cost platform repr) (define repr-costs (platform-representation-costs platform)) (hash-ref repr-costs (representation-name repr))) ; Cost model of a single node by a platform. ; Returns a procedure that must be called with the costs of the children. (define ((platform-node-cost-proc platform) expr repr) (match expr [(? literal?) (lambda () (platform-repr-cost platform repr))] [(? symbol?) (lambda () (platform-repr-cost platform repr))] [(list impl args ...) (define impl-cost (impl-info impl 'cost)) (define impl-agg (impl-info impl 'aggregate)) (lambda itype-costs (unless (= (length itype-costs) (length args)) (error 'platform-node-cost-proc "arity mismatch, expected ~a arguments" (length args))) (+ impl-cost (apply impl-agg itype-costs)))])) ; Cost model parameterized by a platform. (define (platform-cost-proc platform) (define node-cost-proc (platform-node-cost-proc platform)) (λ (expr repr) (let loop ([expr expr] [repr repr]) (match expr [(? literal?) ((node-cost-proc expr repr))] [(? symbol?) ((node-cost-proc expr repr))] [(approx _ impl) (loop impl repr)] [(list impl args ...) (define cost-proc (node-cost-proc expr repr)) (define itypes (impl-info impl 'itype)) (apply cost-proc (map loop args itypes))])))) ;; Extracts the `fpcore` field of an operator implementation ;; as a property dictionary and expression. (define (impl->fpcore impl) (match (impl-info impl 'fpcore) [(list '! props ... body) (values (props->dict props) body)] [body (values '() body)])) (define/reset op-hash #f) ;; For a given FPCore operator, rounding context, and input representations, ;; finds the best operator implementation. Panics if none can be found. (define/contract (get-fpcore-impl op prop-dict ireprs) (-> symbol? prop-dict/c (listof representation?) (or/c symbol? #f)) (unless (op-hash) (define h (make-hash)) (for ([impl (in-list (platform-impls (*active-platform*)))]) (define-values (_ expr) (impl->fpcore impl)) (define expr* (if (symbol? expr) (list expr) expr)) (when (list? expr*) (hash-update! h (car expr*) (curry cons impl) '()))) (op-hash h)) ; gather all implementations that have the same spec, input representations, ; and its FPCore translation has properties that are found in `prop-dict` (define impls (reap [sow] (for ([impl (in-list (hash-ref (op-hash) op '()))] #:when (equal? ireprs (impl-info impl 'itype))) (define-values (prop-dict* expr) (impl->fpcore impl)) (define expr* (if (symbol? expr) (list expr) expr)) ; Handle named constants (define pattern (cons op (map (lambda (_) (gensym)) ireprs))) (when (and (subset? prop-dict* prop-dict) (pattern-match pattern expr*)) (sow impl))))) ; check that we have any matching impls (cond [(null? impls) #f] [else ; we rank implementations and select the highest scoring one (define scores (for/list ([impl (in-list impls)]) (define-values (prop-dict* _) (impl->fpcore impl)) (define num-matching (count (lambda (prop) (member prop prop-dict*)) prop-dict)) (cons num-matching (- (length prop-dict) num-matching)))) ; select the best implementation ; sort first by the number of matched properties, ; then tie break on the number of extraneous properties (match-define (list (cons _ best) _ ...) (sort (map cons scores impls) (lambda (x y) (cond [(> (car x) (car y)) #t] [(< (car x) (car y)) #f] [else (> (cdr x) (cdr y))])) #:key car)) best])) (define (display-platform platform) (define impls (platform-implementations platform)) (define reprs (platform-representations platform)) (define repr-costs (platform-representation-costs platform)) (displayln "Representations:") (define reprs-data (for/list ([repr (in-hash-values reprs)] [n (in-naturals)]) (match-define (representation name type _ _ _ _ total-bits _) repr) (define cost (hash-ref repr-costs name)) (list n name type total-bits cost))) (write-table reprs-data (list "idx" "name" "type" "#bits" "cost")) (displayln "\nImplementations") (define impls-data (for/list ([impl (in-hash-values impls)] [n (in-naturals)]) (define name (operator-impl-name impl)) (define itype (map representation-name (context-var-reprs (operator-impl-ctx impl)))) (define otype (representation-name (context-repr (operator-impl-ctx impl)))) (define spec (operator-impl-spec impl)) (define cost (operator-impl-cost impl)) (list n name itype otype spec cost))) (write-table impls-data (list "idx" "name" "itype" "otype" "spec" "cost"))) (define (write-table data headers #:buffer-space [buffer-space 2]) (define row-length (length (car data))) (define cell-widths (make-vector row-length 0)) ; Measure cell-lengths (for ([header (in-list headers)] [i (in-naturals)]) (vector-set! cell-widths i (max (+ (string-length header) buffer-space) (vector-ref cell-widths i)))) (for ([row (in-list data)]) (for ([elem row] [i (in-naturals)]) (vector-set! cell-widths i (max (+ (string-length (~a elem)) buffer-space) (vector-ref cell-widths i))))) ; Header (printf "~a" (~a (list-ref headers 0) #:width (vector-ref cell-widths 0))) (for ([i (in-range 1 row-length)]) (printf "|~a" (~a (list-ref headers i) #:width (vector-ref cell-widths i)))) (newline) (printf "~a" (~a "" #:width (vector-ref cell-widths 0) #:right-pad-string "-")) (for ([i (in-range 1 row-length)]) (printf "+~a" (~a "" #:width (vector-ref cell-widths i) #:right-pad-string "-"))) (newline) ; Content (for ([row data]) (printf "~a" (~a (list-ref row 0) #:width (vector-ref cell-widths 0))) (for ([i (in-range 1 row-length)]) (printf "|~a" (~a (list-ref row i) #:width (vector-ref cell-widths i)))) (newline))) ================================================ FILE: src/syntax/read.rkt ================================================ #lang racket (require "../utils/common.rkt" "../utils/errors.rkt" "platform.rkt" "sugar.rkt" "syntax-check.rkt" "syntax.rkt" "type-check.rkt" "types.rkt") (provide (struct-out test) test-context test-output-repr test-var-reprs load-tests parse-test) (define (free-variables prog) (match prog [(? literal?) '()] [(? number?) '()] [(? symbol?) (list prog)] [(approx _ impl) (free-variables impl)] [(list _ args ...) (remove-duplicates (append-map free-variables args))])) (struct test (name identifier vars input output expected spec pre output-repr-name var-repr-names) #:prefab) (define (test-output-repr test) (get-representation (test-output-repr-name test))) (define (test-var-reprs test) (map get-representation (map cdr (test-var-repr-names test)))) (define (test-context test) (define output-repr (get-representation (test-output-repr-name test))) (define vars (test-vars test)) (define var-reprs (for/list ([var vars]) (get-representation (dict-ref (test-var-repr-names test) var)))) (context (test-vars test) output-repr var-reprs)) ;; Unfortunately copied from `src/syntax/sugar.rkt` (define (expand stx) (match stx ; expand let statements [#`(let* ([#,vars #,vals] ...) #,body) (datum->syntax #f (list 'let* (for/list ([var (in-list vars)] [val (in-list vals)]) (list var (expand val))) (expand body)) stx)] [#`(let ([#,vars #,vals] ...) #,body) (datum->syntax #f (list 'let (for/list ([var (in-list vars)] [val (in-list vals)]) (list var (expand val))) (expand body)) stx)] ; special nullary operators [#`(,(or 'and 'or)) (datum->syntax #f 'TRUE stx)] [#`(+) (warn 'nullary-operator "+ is deprecated as a nullary operator") (datum->syntax #f 0 stx)] [#`(*) (warn 'nullary-operator "* is deprecated as a nullary operator") (datum->syntax #f 1 stx)] ; special unary operators [#`(,(or 'and 'or) #,a) (expand a)] ; deprecated unary operators [#`(,(and (or '+ '*) op) #,a) (warn 'unary-operator "~a is deprecated as a unary operator" op) (expand a)] [#`(/ #,a) (warn 'unary-operator "/ is deprecated as a unary operator") (datum->syntax #f (list '/ 1 (expand a)) stx)] ; binary operators [#`(,(and (or '+ '- '* '/ 'or) op) #,arg1 #,arg2) (datum->syntax #f (list op (expand arg1) (expand arg2)) stx)] ; variary operators [#`(,(and (or '+ '- '* '/ 'or) op) #,arg1 #,arg2 #,rest ...) (unless (null? rest) (warn 'variary-operator "~a is deprecated as a variary operator" op)) (define prev (datum->syntax #f (list op (expand arg1) (expand arg2)) stx)) (let loop ([prev prev] [rest rest]) (match rest [(list) prev] [(list next rest ...) (define prev* (datum->syntax #f (list op prev (expand next)) next)) (loop prev* rest)]))] [#`(,(and (or '< '<= '> '>= '=) op) #,args ...) (define args* (map expand args)) (define out (for/fold ([out #f]) ([term args*] [next (cdr args*)]) (datum->syntax #f (if out (list 'and out (list op term next)) (list op term next)) term))) (or out (datum->syntax #f 'TRUE stx))] [#`(!= #,args ...) (define args* (map expand args)) (define out (for/fold ([out #f]) ([term args*] [i (in-naturals)] #:when #t [term2 args*] [j (in-naturals)] #:when (< i j)) (datum->syntax #f (if out (list 'and out (list '!= term term2)) (list '!= term term2)) stx))) (or out (datum->syntax #f 'TRUE stx))] ; other operators [#`(#,op #,args ...) (datum->syntax #f (cons op (map expand args)) stx)] ; numbers, variables [_ stx])) (define (expand-core stx) (match stx [#`(FPCore #,name (#,vars ...) #,props ... #,body) (datum->syntax #f (append (list 'FPCore name vars) props (list (expand body))) stx)] [#`(FPCore (#,vars ...) #,props ... #,body) (datum->syntax #f (append (list 'FPCore vars) props (list (expand body))) stx)])) (define (parse-platform-name ann) (match ann [(list '! props ... body) (define dict (props->dict props)) (dict-ref dict ':herbie-platform #f)] [_ #f])) (define (parse-test stx) (assert-program! stx) (define stx* (expand-core stx)) (define-values (output-repr ctx) (assert-program-typed! stx*)) (define-values (func-name args props body) (match (syntax->datum stx*) [(list 'FPCore name (list args ...) props ... body) (values name args props body)] [(list 'FPCore (list args ...) props ... body) (values #f args props body)])) ;; NOTE: We intentionally do not use (define prop-dict (props->dict props)) here ;; despite its apparent efficiency. props->dict could be considered, if :alt was a unique key ;; in our property. When there are multiple entries with the same key, props->dict would collapse ;; them, and prevent mutliple Developer Targets from being represented correctly. ;; This less efficient implementation preserves all entries, and maintains dict-ref functionality. (define prop-dict (let loop ([props props]) (match props ['() '()] [(list prop val rest ...) (cons (cons prop val) (loop rest))]))) (define default-prec (dict-ref prop-dict ':precision (*default-precision*))) (define var-names (context-vars ctx)) (define var-reprs (context-var-reprs ctx)) ;; Named fpcores need to be added to function table (when func-name (register-function! func-name args default-prec body)) ;; Try props first, then identifier, else the expression itself (define name (or (dict-ref prop-dict ':name #f) func-name body)) ;; inline and desugar (define body* (fpcore->prog body ctx)) (define pre* (fpcore->prog (dict-ref prop-dict ':pre 'TRUE) ctx)) (define targets (for/list ([(key val) (in-dict prop-dict)] #:when (eq? key ':alt)) (match (parse-platform-name val) ; plat-name is symbol or #f ; If plat-name extracted, check if name matches [(? symbol? plat-name) (cons val (equal? (~a plat-name) (*platform-name*)))] ; try to lower [#f (with-handlers ([exn:fail:user:herbie:missing? (lambda (e) (cons val #f))]) ; Testing if error thrown (fpcore->prog val ctx) (cons val #t))]))) (define spec (fpcore->prog (dict-ref prop-dict ':spec body) ctx)) (check-unused-variables var-names body* pre*) (check-weird-variables var-names) (test (~a name) func-name var-names body* targets (dict-ref prop-dict ':herbie-expected #t) spec pre* (representation-name output-repr) (for/list ([var (in-list var-names)] [repr (in-list var-reprs)]) (cons var (representation-name repr))))) (define (check-unused-variables vars precondition expr) ;; Fun story: you might want variables in the precondition that ;; don't appear in the `expr`, because that can allow you to do ;; non-uniform sampling. For example, if you have the precondition ;; `(< x y)`, where `y` is otherwise unused, then `x` is sampled ;; non-uniformly (biased toward small values). (define used (set-union (free-variables expr) (free-variables precondition))) (unless (set=? vars used) (define unused (set-subtract vars used)) (warn 'unused-variable #:url "faq.html#unused-variable" "unused ~a ~a" (if (equal? (set-count unused) 1) "variable" "variables") (string-join (map ~a unused) ", ")))) (define (check-weird-variables vars) (for ([var (in-list vars)]) (define const-name (string->symbol (string-upcase (symbol->string var)))) (when (operator-exists? const-name) (warn 'strange-variable #:url "faq.html#strange-variable" "unusual variable ~a; did you mean ~a?" var const-name)))) (define (our-read-syntax port name) (parameterize ([read-decimal-as-inexact false]) (read-syntax port name))) (define (load-port port) (port-count-lines! port) (for/list ([test (in-port (curry our-read-syntax "stdin") port)]) (parse-test test))) (define (load-file file) (call-with-input-file file (λ (port) (port-count-lines! port) (for/list ([test (in-port (curry our-read-syntax file) port)]) (parse-test test))))) (define (load-directory dir) (apply append (for/list ([fname (in-directory dir)] #:when (file-exists? fname) #:when (equal? (filename-extension fname) #"fpcore")) (load-file fname)))) (define (load-tests path) (define path* (if (string? path) (string->path path) path)) (define out (cond [(port? path) (load-port path)] [(equal? path "-") (load-port (current-input-port))] [(directory-exists? path*) (load-directory path*)] [else (load-file path*)])) (define duplicates (find-duplicates (map test-name out))) (unless (null? duplicates) (warn 'duplicate-names "Duplicate ~a ~a used for multiple cores" (if (equal? (length duplicates) 1) "name" "names") (string-join (map (curry format "\"~a\"") duplicates) ", "))) out) (module+ test (require rackunit "../syntax/float.rkt" "../syntax/load-platform.rkt") (activate-platform! (*platform-name*)) (define precision 'binary64) (define ctx (context '(x y z a) (make-list 4 ))) ;; inlining ;; Test classic quadp and quadm examples (register-function! 'discr (list 'a 'b 'c) precision `(sqrt (- (* b b) (* a c)))) (define quadp `(/ (+ (- y) (discr x y z)) x)) (define quadm `(/ (- (- y) (discr x y z)) x)) (check-equal? (fpcore->prog quadp ctx) '(/.f64 (+.f64 (neg.f64 y) (sqrt.f64 (-.f64 (*.f64 y y) (*.f64 x z)))) x)) (check-equal? (fpcore->prog quadm ctx) '(/.f64 (-.f64 (neg.f64 y) (sqrt.f64 (-.f64 (*.f64 y y) (*.f64 x z)))) x)) ;; x^5 = x^3 * x^2 (register-function! 'sqr (list 'x) precision '(* x x)) (register-function! 'cube (list 'x) precision '(* x x x)) (define fifth '(* (cube a) (sqr a))) (check-equal? (fpcore->prog fifth ctx) '(*.f64 (*.f64 (*.f64 a a) a) (*.f64 a a))) ;; casting edge cases (check-equal? (fpcore->prog `(cast x) ctx) 'x) (check-equal? (fpcore->prog `(cast (! :precision binary64 x)) ctx) 'x)) ================================================ FILE: src/syntax/rival.rkt ================================================ ;; A narrow shim for Rival's "machine" abstraction. ;; A Rival "machine" performs real evaluation for multiple expressions on a point. ;; ;; Ensure this file has minimal dependencies since `/syntax/syntax.rkt` ;; requires the file to synthesize floating-point implementations! ;; #lang racket (require math/bigfloat (prefix-in r2: rival) (prefix-in r3: rival3)) (require "../config.rkt" "../core/arrays.rkt" "../utils/errors.rkt" "../syntax/float.rkt" "../utils/timeline.rkt" "../syntax/types.rkt" "../syntax/batch.rkt") (define (use-rival3?) (not (flag-set? 'setup 'rival2))) (define-syntax-rule (define/rival (name args ...) r2-impl r3-impl) (define (name args ...) (if (use-rival3?) (r3-impl args ...) (r2-impl args ...)))) (define/rival (rival-compile exprs vars discs) r2:rival-compile r3:rival-compile) (define/rival (rival-apply machine pt hint) r2:rival-apply r3:rival-apply) (define/rival (rival-analyze-with-hints machine rect hint) r2:rival-analyze-with-hints r3:rival-analyze-with-hints) (define/rival (rival-profile machine param) r2:rival-profile r3:rival-profile) (define (repr->disc-type repr) (cond [(eq? repr ) 'bool] [(eq? repr ) 'f32] [(eq? repr ) 'f64] [else (error 'repr->disc-type "unsupported repr ~a" (representation-name repr))])) (define (repr->disc-convert repr) (if (eq? repr ) (r3:discretization-convert r3:boolean-discretization) (representation-bf->repr repr))) (define (make-discretizations reprs) (if (use-rival3?) ;; Rival 3 requires that all discretizations share the target precision for now (let ([target (apply max (map representation-total-bits reprs))]) (cons (struct-copy r3:discretization r3:boolean-discretization [target target]) (for/list ([repr (in-list reprs)]) (r3:discretization (repr->disc-type repr) target (repr->disc-convert repr))))) (cons r2:boolean-discretization (for/list ([repr (in-list reprs)]) (define ulps (repr-ulps repr)) (r2:discretization (representation-total-bits repr) (representation-bf->repr repr) (lambda (x y) (- (ulps x y) 1))))))) (define (exn:rival:invalid? e) (or (r2:exn:rival:invalid? e) (r3:exn:rival:invalid? e))) (define (exn:rival:unsamplable? e) (or (r2:exn:rival:unsamplable? e) (r3:exn:rival:unsamplable? e))) (define *rival-max-precision* (make-derived-parameter r2:*rival-max-precision* identity (lambda (v) (r3:*rival-max-precision* v) v))) (define *rival-max-iterations* (make-derived-parameter r2:*rival-max-iterations* identity (lambda (v) (r3:*rival-max-iterations* v) v))) (define (*rival-profile-executions*) (if (use-rival3?) (r3:*rival-profile-executions*) (r2:*rival-profile-executions*))) (define/rival (execution-name exec) r2:execution-name r3:execution-name) (define/rival (execution-precision exec) r2:execution-precision r3:execution-precision) (define/rival (execution-time exec) r2:execution-time r3:execution-time) (define/rival (execution-memory exec) r2:execution-memory r3:execution-memory) (struct herbie-ival (lo hi) #:transparent) (define (ival? x) (or (herbie-ival? x) (r2:ival? x) (r3:ival? x))) (define (ival lo hi) (herbie-ival lo hi)) (define (ival-lo iv) (cond [(herbie-ival? iv) (herbie-ival-lo iv)] [(r3:ival? iv) (r3:ival-lo iv)] [else (r2:ival-lo iv)])) (define (ival-hi iv) (cond [(herbie-ival? iv) (herbie-ival-hi iv)] [(r3:ival? iv) (r3:ival-hi iv)] [else (r2:ival-hi iv)])) (provide (struct-out real-compiler) ival ival? ival-lo ival-hi (contract-out [make-real-compiler (->i ([batch batch?] [brfs (listof batchref?)] [ctxs (brfs) (and/c unified-contexts? (lambda (ctxs) (= (length brfs) (length ctxs))))]) (#:pre [pre any/c]) [c real-compiler?])] [real-apply (->* (real-compiler? vector?) (any/c) (values symbol? any/c))] [real-compiler-clear! (-> real-compiler? void?)] [real-compiler-analyze (->* (real-compiler? (vectorof ival?)) (any/c) (listof any/c))])) (define (unified-contexts? ctxs) (cond [((non-empty-listof context?) ctxs) (define ctx0 (car ctxs)) (for/and ([ctx (in-list (cdr ctxs))]) (and (equal? (context-vars ctx0) (context-vars ctx)) (for/and ([var (in-list (context-vars ctx0))]) (equal? (context-lookup ctx0 var) (context-lookup ctx var)))))] [else #f])) (define (expr-size expr) (if (list? expr) (apply + 1 (map expr-size (cdr expr))) 1)) ;; Herbie's wrapper around the Rival machine abstraction. (struct real-compiler (pre vars var-reprs exprs reprs machine dump-file assemble-point assemble-output)) ;; Creates a Rival machine. (define (make-real-compiler batch brfs ctxs #:pre [pre '(TRUE)]) (define specs (map (batch-exprs batch) brfs)) (define-values (specs* ctxs* pre* assemble-point assemble-output reprs) (flatten-arrays-for-rival specs ctxs pre)) (define vars (context-vars (first ctxs*))) ; create the machine (define exprs (cons `(assert ,pre*) specs*)) (define discs (make-discretizations reprs)) (define machine (rival-compile exprs vars discs)) (timeline-push! 'compiler (apply + 1 (expr-size pre*) (map expr-size specs*)) (+ (length vars) (rival-profile machine 'instructions))) (define dump-file (cond [(flag-set? 'dump 'rival) (define dump-dir "dump-rival") (unless (directory-exists? dump-dir) (make-directory dump-dir)) (define name (for/first ([i (in-naturals)] #:unless (file-exists? (build-path dump-dir (format "~a.rival" i)))) (build-path dump-dir (format "~a.rival" i)))) (define dump-file (open-output-file name #:exists 'replace)) (pretty-print `(define (f ,@vars) ,@specs*) dump-file 1) (flush-output dump-file) dump-file] [else #f])) ; wrap it with useful information for Herbie (real-compiler pre (list->vector vars) (list->vector (context-var-reprs (first ctxs*))) specs* (list->vector reprs) machine dump-file assemble-point assemble-output)) (define (bigfloat->readable-string x) (define real (bigfloat->real x)) ; Exact rational unless inf/nan (define float (real->double-flonum real)) (if (= real float) (format "#i~a" float) ; The #i explicitly means nearest float (number->string real))) ; Backup is print as rational ;; Runs a Rival machine on an input point. (define (real-apply compiler pt [hint #f]) (match-define (real-compiler _ vars var-reprs _ _ machine dump-file _ _) compiler) (define start (current-inexact-milliseconds)) (define pt* (for/vector #:length (vector-length vars) ([val (in-vector pt)] [repr (in-vector var-reprs)]) ((representation-repr->bf repr) val))) (when dump-file (define args (map bigfloat->readable-string (vector->list pt*))) (fprintf dump-file "(eval f ~a)\n" (string-join args " ")) (flush-output dump-file)) (define-values (status value) (with-handlers ([exn:rival:invalid? (lambda (e) (values 'invalid #f))] [exn:rival:unsamplable? (lambda (e) (values 'exit #f))]) (parameterize ([*rival-max-precision* (*max-mpfr-prec*)] [*rival-max-iterations* 5]) (define value (rest (vector->list (rival-apply machine pt* hint)))) ; rest = drop precondition (values 'valid value)))) (when (> (rival-profile machine 'bumps) 0) (warn 'ground-truth "Could not converge on a ground truth" #:extra (for/list ([var (in-vector vars)] [val (in-vector pt)]) (format "~a = ~a" var val)))) (define-values (iterations mixsample-data) (if (use-rival3?) (match-let ([(list summary _ iters) (rival-profile machine 'summary)]) (values iters (for/list ([entry (in-vector summary)]) (match-define (list name prec-bucket total-time _) entry) (list total-time name prec-bucket 0)))) (let () (define executions (rival-profile machine 'executions)) (when (>= (vector-length executions) (r2:*rival-profile-executions*)) (warn 'profile "Rival profile vector overflowed, profile may not be complete")) (define prec-threshold (exact-floor (/ (*max-mpfr-prec*) 25))) (define mixsample-table (make-hash)) (for ([execution (in-vector executions)]) (define name (format "~a" (r2:execution-name execution))) (define precision (- (r2:execution-precision execution) (remainder (r2:execution-precision execution) prec-threshold))) (define key (cons name precision)) ;; Uses vectors to avoid allocation; this is really allocation-heavy (define data (hash-ref! mixsample-table key (lambda () (make-vector 2 0)))) (vector-set! data 0 (+ (vector-ref data 0) (r2:execution-time execution))) (vector-set! data 1 (+ (vector-ref data 1) (r2:execution-memory execution)))) (values (rival-profile machine 'iterations) (for/list ([(key val) (in-hash mixsample-table)]) (list (vector-ref val 0) (car key) (cdr key) (vector-ref val 1))))))) (for ([entry (in-list mixsample-data)]) (match-define (list time name prec memory) entry) (timeline-push!/unsafe 'mixsample time name prec memory)) (timeline-push!/unsafe 'outcomes (- (current-inexact-milliseconds) start) iterations (symbol->string status) 1) (values status value)) ;; Clears profiling data. (define (real-compiler-clear! compiler) (unless (use-rival3?) (r2:rival-profile (real-compiler-machine compiler) 'executions)) (void)) ;; Returns whether the machine is guaranteed to raise an exception ;; for the given inputs range. The result is an interval representing ;; how certain the result is: no, maybe, yes. (define (real-compiler-analyze compiler input-ranges [hint #f]) (define rect* (for/vector #:length (vector-length input-ranges) ([iv (in-vector input-ranges)]) (if (use-rival3?) (r3:ival (ival-lo iv) (ival-hi iv)) (r2:ival (ival-lo iv) (ival-hi iv))))) (rival-analyze-with-hints (real-compiler-machine compiler) rect* hint)) (module+ test (require rackunit) (define ) (define arr-repr (make-array-representation #:elem #:len 3)) (define arr-ctx (context '(v) arr-repr (list arr-repr))) (define-values (specs* ctxs* pre* _assemble-pt _assemble-out reprs*) (flatten-arrays-for-rival (list 'v) (list arr-ctx) 'TRUE)) (check-equal? specs* '(v_0 v_1 v_2)) (check-equal? (map context-vars ctxs*) '((v_0 v_1 v_2))) (check-equal? (map context-var-reprs ctxs*) (list (list ))) (check-equal? reprs* (list )) (check-equal? (_assemble-out '(1 2 3)) '(#(1 2 3))) (check-equal? pre* 'TRUE)) ================================================ FILE: src/syntax/sugar.rkt ================================================ ;; Expression conversions ;; ;; Herbie uses three expression languages. ;; All formats are S-expressions with variables, numbers, and applications. ;; ;; FPCore: the input/output language ;; ;; - standardized interchange format with other tools ;; - using a loopless subset with array literals ;; - operators denote real computations while rounding contexts decide format ;; ;; ::= (FPCore ( ...) ... ) ;; | (FPCore ( ...) ... ) ;; ;; ::= (let ([ ] ...) ) ;; | (let* ([ ] ...) ) ;; | (if ;; | (cast ) ;; | (! ... ) ;; | ( ...) ;; | ;; | ;; ;; ::= ;; | (! ... ) ;; ;; LImpl: the language of floating-point expressions ;; ;; - internal language, describes floating-point expressions ;; - unary minus represented by `neg` ;; - every operator is rounded ;; - let expressions are inlined ;; - numbers are rounded to a particular format ;; ;; ::= ( ...) ;; | (literal ) ;; | ;; ;; Every operator has a type signature where types are representations. ;; In practice, most operator implemenetations have uniform representations but ;; operations like casts are multi-format. ;; ;; LSpec: the language of mathematical formula ;; ;; - internal language, describes real-number formula ;; - unary minus represented by `neg` ;; - every operator is a real-number operation ;; - every operator is supported by Rival ;; - let expressions are inlined ;; - casts are mapped to the identity operation ;; - numbers are formatless ;; ;; ::= ( ...) ;; | ;; | ;; #lang racket (require "../utils/common.rkt" "../utils/errors.rkt" "platform.rkt" "matcher.rkt" "syntax.rkt" "types.rkt") (provide fpcore->prog prog->fpcore) ;; Local copies to avoid depending on core/programs.rkt. (define (repr-of expr ctx) (match expr [(literal _ precision) (get-representation precision)] [(? symbol?) (context-lookup ctx expr)] [(approx _ impl) (repr-of impl ctx)] [(hole precision _) (get-representation precision)] [(list op _ ...) (impl-info op 'otype)])) (define (replace-vars dict expr) (let loop ([expr expr]) (match expr [(? literal?) expr] [(? number?) expr] [(? symbol?) (dict-ref dict expr expr)] [(approx impl spec) (approx (loop impl) (loop spec))] [(list op args ...) (cons op (map loop args))]))) ;; Expression pre-processing for normalizing expressions. ;; Used for conversion from FPCore to other IRs. (define (expand-expr expr) (let loop ([expr expr] [env '()]) (match expr ; empty let/let* expression [`(,(or 'let 'let*) () ,body) (loop body env)] ; let* expression [`(let* ([,var ,val] ,rest ...) ,body) (loop `(let ([,var ,val]) (let* ,rest ,body)) env)] ; let expression [`(let ([,vars ,vals] ...) ,body) (define env* (for/fold ([env* env]) ([var (in-list vars)] [val (in-list vals)]) (dict-set env* var (loop val env)))) (loop body env*)] ; nullary expressions ['(and) '(TRUE)] ['(or) '(FALSE)] [`(,(or '+ '-)) 0] [`(,(or '* '/)) 1] ; unary expressions [`(,(or '+ '* 'and 'or) ,a) (loop a env)] [`(- ,a) `(neg ,(loop a env))] [`(/ ,a) `(/ 1 ,(loop a env))] ; expand arithmetic associativity [`(,(and (or '+ '- '* '/ 'and 'or) op) ,as ..2 ,b) (list op (loop `(,op ,@as) env) (loop b env))] ; expand comparison associativity [`(,(and (or '< '<= '> '>= '=) op) ,as ...) (define as* (map (curryr loop env) as)) (define out (for/fold ([out #f]) ([term as*] [next (cdr as*)]) (if out (list 'and out (list op term next)) (list op term next)))) (or out '(TRUE))] [`(!= ,as ...) (define as* (map (curryr loop env) as)) (define out (for/fold ([out #f]) ([term as*] [i (in-naturals)] #:when true [term2 as*] [j (in-naturals)] #:when (< i j)) (if out (list 'and out (list '!= term term2)) (list '!= term term2)))) (or out '(TRUE))] ; function calls [(list (? (curry hash-has-key? (*functions*)) fname) args ...) (match-define (list vars _ body) (hash-ref (*functions*) fname)) (define env* (for/fold ([env* '()]) ([var (in-list vars)] [arg (in-list args)]) (dict-set env* var (loop arg env)))) (loop body env*)] ; applications [`(,op ,args ...) `(,op ,@(map (curryr loop env) args))] ; constants [(? operator-exists? op) (list expr)] ; variables [(? symbol?) (dict-ref env expr expr)] ; other [_ expr]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; FPCore -> LImpl (define (assert-fpcore-impl op prop-dict ireprs) (or (get-fpcore-impl op prop-dict ireprs) (raise-herbie-missing-error "No implementation for `~a` under rounding context `~a` with types `~a`" op prop-dict (string-join (map (λ (r) (format "<~a>" (representation-name r))) ireprs) " ")))) ;; Translates an FPCore operator application into ;; an LImpl operator application. (define (fpcore->impl-app op prop-dict args ctx) (define ireprs (map (lambda (arg) (repr-of arg ctx)) args)) (define impl (assert-fpcore-impl op prop-dict ireprs)) (define vars (impl-info impl 'vars)) (define pattern (match (impl-info impl 'fpcore) [(list '! _ ... body) body] [body body])) (define subst (pattern-match pattern (cons op args))) (pattern-substitute (cons impl vars) subst)) ;; Translates from FPCore to an LImpl. (define (fpcore->prog prog ctx) (let loop ([expr (expand-expr prog)] [prop-dict (repr->prop (context-repr ctx))]) (match expr [(? number? n) (literal (match n [(or +inf.0 -inf.0 +nan.0) expr] [(? exact?) expr] [_ (inexact->exact expr)]) (dict-ref prop-dict ':precision))] [(? symbol?) expr] [(list '! props ... body) (loop body (if (not (null? props)) (apply dict-set* prop-dict props) prop-dict))] [(list 'neg arg) ; non-standard but useful [TODO: remove] (define arg* (loop arg prop-dict)) (fpcore->impl-app '- prop-dict (list arg*) ctx)] [(list 'cast arg) ; special case: unnecessary casts (define arg* (loop arg prop-dict)) (define repr (get-representation (dict-ref prop-dict ':precision))) (if (equal? (repr-of arg* ctx) repr) arg* (fpcore->impl-app 'cast prop-dict (list arg*) ctx))] [(list 'ref arr idx) (define arr* (loop arr prop-dict)) (define idx* (loop idx prop-dict)) (define arr-repr (repr-of arr* ctx)) (define idx-repr (repr-of idx* ctx)) (define impl (assert-fpcore-impl 'ref prop-dict (list arr-repr idx-repr))) (define vars (impl-info impl 'vars)) (define pattern (match (impl-info impl 'fpcore) [(list '! _ ... body) body] [body body])) (define subst (pattern-match pattern (list 'ref arr* idx*))) (pattern-substitute (cons impl vars) subst)] [(list op args ...) (define args* (map (lambda (arg) (loop arg prop-dict)) args)) (fpcore->impl-app op prop-dict args* ctx)]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; LImpl -> FPCore ;; Translates from LImpl to an FPCore ;; TODO: this process uses a batch-like data structure ;; but _without_ deduplication since different use sites ;; of a particular subexpression may have different ;; parent rounding contexts. Would be nice to explore ;; if the batch data structure can be used. ;; Instruction vector index (struct index (v) #:prefab) ;; Translates a literal (LImpl) to an FPCore expr (define (literal->fpcore x) (match x [(literal -inf.0 _) '(- INFINITY)] [(literal +inf.0 _) 'INFINITY] [(literal v (or 'binary64 'binary32)) (exact->inexact v)] [(literal v _) v])) ;; Step 1. ;; Translates from LImpl to a series of let bindings such that each ;; local variable is bound once and used at most once. The result is an ;; instruction vector, representing the let bindings; the operator ;; implementation for each instruction, and the final "root" operation/literal. ;; Except for let-bound variables, the subexpressions are in FPCore. (define (prog->let-exprs expr) (define instrs '()) (define (push! impl node) (define id (length instrs)) (set! instrs (cons (cons node impl) instrs)) (index id)) (define (munge expr #:root? [root? #f]) (match expr [(? literal?) (literal->fpcore expr)] [(? number?) expr] [(? symbol?) expr] [(approx _ impl) (munge impl)] [(hole _ spec) (munge spec)] [(list (? impl-exists? impl) args ...) (define args* (map munge args)) (define vars (impl-info impl 'vars)) (define node (replace-vars (map cons vars args*) (impl-info impl 'fpcore))) (if root? node (push! impl node))] [(list spec-op) spec-op] [(list spec-op args ...) (list* spec-op (map munge args))])) (define root (munge expr #:root? #t)) (cons (list->vector (reverse instrs)) root)) ;; Step 2. ;; Inlines let bindings; let-inlining is generally unsound with ;; rounding properties (the parent context may change), ;; so we only inline those that result in the same operator ;; implementation when converting back from FPCore to LImpl. (define (inline! root ivec ctx) (define global-prop-dict (repr->prop (context-repr ctx))) (let loop ([node root] [prop-dict global-prop-dict]) (match node [(? number?) node] ; number [(? symbol?) node] ; variable [(index idx) ; let-bound variable ; we check what happens if we inline (match-define (cons expr impl) (vector-ref ivec idx)) (define impl* (match expr [(list '! props ... (or (? symbol? op) (list op _ ...))) ; rounding context updated parent context (define prop-dict* (if (not (null? props)) (apply dict-set prop-dict props) prop-dict)) (assert-fpcore-impl op prop-dict* (impl-info impl 'itype))] ; rounding context inherited from parent context [(or (? symbol? op) (list op _ ...)) (assert-fpcore-impl op prop-dict (impl-info impl 'itype))])) (cond [(equal? impl impl*) ; inlining is safe (define expr* (loop expr prop-dict)) (vector-set! ivec idx #f) expr*] [else ; inlining is not safe (define expr* (loop expr global-prop-dict)) (vector-set! ivec idx expr*) node])] [(list '! props ... body) ; explicit rounding context (define prop-dict* (props->dict props)) (define body* (loop body prop-dict*)) (define new-prop-dict (for/list ([(k v) (in-dict prop-dict*)] #:unless (and (dict-has-key? prop-dict k) (equal? (dict-ref prop-dict k) v))) (cons k v))) (if (null? new-prop-dict) body* `(! ,@(append* (for/list ([(k v) (in-list new-prop-dict)]) (list k v))) ,body*))] [(list op args ...) ; operator application (define args* (map (lambda (e) (loop e prop-dict)) args)) `(,op ,@args*)]))) ;; Step 3. ;; Construct the final FPCore expression using remaining let-bindings ;; and the let-free body from the previous step. (define (reachable-indices ivec expr) (define reachable (mutable-set)) (let loop ([expr expr]) (match expr [(? number?) (void)] [(? symbol?) (void)] [(index idx) (set-add! reachable idx) (loop (vector-ref ivec idx))] [(list _ args ...) (for-each loop args)])) reachable) (define (remove-indices id->name expr) (let loop ([expr expr]) (match expr [(? number?) expr] [(? symbol?) expr] [(index idx) (hash-ref id->name idx)] [(list '! props ... body) `(! ,@props ,(loop body))] [(list op args ...) `(,op ,@(map loop args))]))) (define (build-expr expr ivec ctx) ; variable generation (define vars (list->mutable-seteq (context-vars ctx))) (define counter 0) (define (gensym) (set! counter (add1 counter)) (match (string->symbol (format "t~a" counter)) [(? (curry set-member? vars)) (gensym)] [x (set-add! vars x) x])) ; need fresh variables for reachable, non-inlined subexpressions (define reachable (reachable-indices ivec expr)) (define id->name (make-hash)) (for ([expr (in-vector ivec)] [idx (in-naturals)] #:when (and expr (set-member? reachable idx))) (hash-set! id->name idx (gensym))) (for/fold ([body (remove-indices id->name expr)]) ([idx (in-list (sort (hash-keys id->name) >))]) (define var (hash-ref id->name idx)) (define val (remove-indices id->name (vector-ref ivec idx))) `(let ([,var ,val]) ,body))) ;; Translates from LImpl to an FPCore. ;; The implementation of this procedure is complicated since ;; (1) every operator implementation requires certain (FPCore) rounding properties ;; (2) rounding contexts have lexical scoping (define (prog->fpcore prog ctx) ; step 1: convert to an instruction vector where ; each expression is evaluated under explicit rounding contexts (match-define (cons ivec root) (prog->let-exprs prog)) ; step 2: inline nodes (define body (inline! root ivec ctx)) ; step 3: construct the actual FPCore expression from ; the remaining let-bindings and body (build-expr body ivec ctx)) ================================================ FILE: src/syntax/syntax-check.rkt ================================================ #lang racket (require syntax/id-set) (require "../utils/common.rkt" "../utils/errors.rkt" "syntax.rkt" "types.rkt" "platform.rkt") (provide assert-program!) (define (check-expression* stx vars error!) (let loop ([stx stx] [vars vars]) (match stx [#`,(? number?) (void)] [#`,(? operator-exists? stx) (void)] [#`,(? symbol? var) (unless (set-member? vars stx) (error! stx "Unknown variable ~a" var))] [#`(let* ([#,vars* #,vals] ...) #,body) (define bindings (for/fold ([vars vars]) ([var vars*] [val vals]) (unless (identifier? var) (error! var "Invalid variable name ~a" var)) (loop val vars) (bound-id-set-union vars (immutable-bound-id-set (list var))))) (loop body bindings)] [#`(let ([#,vars* #,vals] ...) #,body) ;; These are unfolded by desugaring (for ([var vars*] [val vals]) (unless (identifier? var) (error! var "Invalid variable name ~a" var)) (loop val vars)) (loop body (bound-id-set-union vars (immutable-bound-id-set vars*)))] [#`(let #,varlist #,body) (error! stx "Invalid `let` expression variable list ~a" (syntax->datum varlist)) (loop body vars)] [#`(let #,args ...) (error! stx "Invalid `let` expression with ~a arguments (expects 2)" (length args)) (unless (null? args) (loop (last args) vars))] [#`(if #,cond #,ift #,iff) (loop cond vars) (loop ift vars) (loop iff vars)] [#`(if #,args ...) (error! stx "Invalid `if` expression with ~a arguments (expects 3)" (length args)) (unless (null? args) (loop (last args) vars))] [#`(! #,props ... #,body) (check-properties* props '() error!) (loop body vars)] [#`(array #,elems ...) (when (null? elems) (error! stx "Array literal must have at least one element")) (for ([elem (in-list elems)]) (loop elem vars))] [#`(ref #,arr ,idx) (unless (integer? idx) (error! idx "Array index must be a literal integer, got ~a" idx)) (loop arr vars)] [#`(cast #,arg) (loop arg vars)] [#`(cast #,args ...) (error! stx "Invalid `cast` expression with ~a arguments (expects 1)" (length args)) (unless (null? args) (loop (first args) vars))] [#`(,(? (curry set-member? '(+ * and or))) #,args ...) ;; Variary (minimum 0 arguments) (for ([arg args]) (loop arg vars))] [#`(,(? (curry set-member? '(- / = != < > <= >=)))) ;; Variary (minimum 1 arg) (error! stx "Variary operator expects at least one argument")] [#`(,(? (curry set-member? '(- / = != < > <= >=))) #,args ...) ;; Variary (minimum 1 arg) (for ([arg args]) (loop arg vars))] [#`(,(? (curry set-member? '(+ - * / and or = != < > <= >=))) #,args ...) ;; These expand by associativity so we don't check the number of arguments (for ([arg args]) (loop arg vars))] [#`(,(? (curry set-member? '(erfc expm1 log1p hypot fma)) op) #,args ...) ; FPCore operators that are composite in Herbie (define arity (case op [(erfc expm1 log1p) 1] [(hypot) 2] [(fma) 3])) (unless (= arity (length args)) (error! stx "Operator ~a given ~a arguments (expects ~a)" op (length args) arity)) (for ([arg (in-list args)]) (loop arg vars))] [#`(#,f-syntax #,args ...) (define f (syntax->datum f-syntax)) (cond [(operator-exists? f) (define arity (length (operator-info f 'itype))) (unless (= arity (length args)) (error! stx "Operator ~a given ~a arguments (expects ~a)" f (length args) arity))] [(hash-has-key? (*functions*) f) (match-define (list vars _ _) (hash-ref (*functions*) f)) (unless (= (length vars) (length args)) (error! stx "Function ~a given ~a arguments (expects ~a)" f (length args) (length vars)))] [else (error! stx "Unknown operator ~a" f)]) (for ([arg args]) (loop arg vars))] [_ (error! stx "Unknown syntax ~a" (syntax->datum 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 ':herbie-platform) (define name (dict-ref prop-dict ':herbie-platform)) (unless (identifier? name) (error! name "Invalid :herbie-platform ~a; must be a symbol" name))) (when (dict-has-key? prop-dict ':precision) (define prec (dict-ref prop-dict ':precision)) (define repr (get-representation (syntax->datum prec))) (unless repr (error! prec "Unknown :precision ~a" prec)) (unless (or (not repr) (equal? (representation-type repr) 'real)) (error! prec "Invalid :precision ~a; expected a real representation name" prec))) (when (dict-has-key? prop-dict ':cite) (define cite (dict-ref prop-dict ':cite)) (if (list? (syntax-e cite)) (for ([citation (syntax-e cite)] #:unless (identifier? citation)) (error! citation "Invalid citation ~a; must be a variable name" citation)) (error! cite "Invalid :cite ~a; must be a list" cite))) (when (dict-has-key? prop-dict ':pre) (check-expression* (dict-ref prop-dict ':pre) vars error!)) (when (dict-has-key? prop-dict ':alt) (check-expression* (dict-ref prop-dict ':alt) vars error!)) (void)) (define (check-argument name dims sow error!) (unless (identifier? name) (error! name "Invalid argument name ~a" name)) (for ([dim (in-list dims)]) (define dim* (syntax-e dim)) (unless (number? dim*) (error! dim "Invalid dimension ~a; must be a positive integer literal" dim)) (when (number? dim*) (unless (exact-positive-integer? dim*) (error! dim "Invalid dimension ~a; dimensions must be positive integers" dim)))) (sow name)) (define (check-program* stx vars props body error!) (unless (list? vars) (error! stx "Invalid arguments list ~a; must be a list" stx)) (define vars* (reap [sow] (when (list? vars) (for ([var (in-list vars)]) (match var [(? identifier? x) (check-argument x '() sow error!)] [#`(! #,props ... #,name #,dims ...) (check-properties* props (immutable-bound-id-set '()) error!) (check-argument name dims sow error!)] [#`(#,name #,dims ...) (check-argument name dims sow error!)] [_ (error! var "Invalid argument name ~a" var)]))))) (when (check-duplicate-identifier vars*) (error! stx "Duplicate argument name ~a" (check-duplicate-identifier vars*))) (check-properties* props (immutable-bound-id-set vars*) error!) (check-expression* body (immutable-bound-id-set vars*) error!)) (define (check-fpcore* stx error!) (match stx [#`(FPCore #,name (#,vars ...) #,props ... #,body) (unless (identifier? name) (error! stx "FPCore identifier must be a symbol: ~a" name)) (check-program* stx vars props body error!)] [#`(FPCore (#,vars ...) #,props ... #,body) (check-program* stx vars props body error!)] [#`(FPCore #,something ...) (error! stx "FPCore not in a valid format: ~s" stx)] [_ (error! stx "Not an FPCore: ~a" stx)])) (define (assert-program! stx) (define errs (reap [sow] (define (error! stx fmt . args) (define args* (for/list ([x (in-list args)]) (if (syntax? x) (syntax->datum x) x))) (sow (cons stx (apply format fmt args*)))) (check-fpcore* stx error!))) (unless (null? errs) (raise-herbie-syntax-error "Invalid program" #:locations errs))) ;; testing FPCore format (module+ test (require rackunit) (require "../syntax/load-platform.rkt") (activate-platform! (*platform-name*)) (define (get-errs stx) (reap [sow] (define (error! stx fmt . args) (define args* (for/list ([x (in-list args)]) (if (syntax? x) (syntax->datum x) x))) (sow (cons stx (apply format fmt args*)))) (check-fpcore* stx error!))) (check-pred (compose not null?) (get-errs #'a)) (check-pred (compose not null?) (get-errs #'(FPCore))) (check-pred (compose not null?) (get-errs #'(FPCore foo 1))) (check-pred (compose not null?) (get-errs #'(FPCore foo x 1))) (check-pred (compose not null?) (get-errs #'(FPCore foo foo2 (x) x))) (check-pred null? (get-errs #'(FPCore (x) x))) (check-pred null? (get-errs #'(FPCore (x) :precision binary64 x))) (check-pred null? (get-errs #'(FPCore foo (x) x))) (check-pred null? (get-errs #'(FPCore foo (x) :precision binary64 x))) (check-pred null? (get-errs #'(FPCore (x) (array 1 2)))) (check-pred null? (get-errs #'(FPCore (x) (ref (array 1 2) 0)))) (check-pred null? (get-errs #'(FPCore (x) (array 1 2 3)))) (check-pred null? (get-errs #'(FPCore (x) (ref (array 1 2) 2)))) (check-pred null? (get-errs #'(FPCore ((v 3)) v))) (check-pred null? (get-errs #'(FPCore ((v 2 2)) v)))) ================================================ FILE: src/syntax/syntax.rkt ================================================ #lang racket (require math/bigfloat racket/hash (only-in rival/eval/main rival-functions)) (require "../utils/common.rkt" "../utils/errors.rkt" "matcher.rkt" "types.rkt") (provide (struct-out literal) (struct-out approx) (struct-out hole) operator-exists? operator-info all-operators ; return a list of operators names *functions* register-function! (struct-out operator-impl)) ; required by platform.rkt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Real operators ;; Pure mathematical operations ;; TODO: specs should really be associated with impls ;; unfortunately Herbie still mandates that every impl ;; has an associated operator so the spec is here ;; All real operators come from Rival ;; Checks if an operator has been registered. (define (operator-exists? op) (or (hash-has-key? rival-functions op) (equal? op 'ref) (equal? op 'array))) ;; Returns all operators. (define (all-operators) (sort (append (hash-keys rival-functions) (list 'array 'ref)) symbol symbol? (or/c 'itype 'otype) any/c) (define info (cond [(equal? op 'ref) '(real array real)] [(equal? op 'array) '(array real real)] [else (hash-ref rival-functions op (lambda () (raise-arguments-error 'operator-info "Unknown operator" "op" op)))])) (match-define (cons otype itypes) info) (case field [(itype) itypes] [(otype) otype])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Operator implementations ;; Floating-point operations that approximate mathematical operations ;; Operator implementations _approximate_ a program of ;; mathematical operators with fixed input and output representations. ;; ;; An operator implementation requires ;; - a (unique) name ;; - input variables/representations ;; - output representation ;; - a specification it approximates ;; - its FPCore representation ;; - a floating-point implementation ;; (struct operator-impl (name ctx spec fpcore fl cost aggregate)) ;; Floating-point expressions require that numbers ;; be rounded to a particular precision. (struct literal (value precision) #:prefab) ;; An approximation of a specification by ;; a floating-point expression. (struct approx (spec impl) #:prefab) ;; An unknown floating-point expression that implements a given spec (struct hole (precision spec) #:prefab) ;; name -> (vars repr body) ;; name -> (vars prec body) (define *functions* (make-parameter (make-hasheq))) (define (register-function! name args repr body) ;; Adds a function definition. (hash-set! (*functions*) name (list args repr body))) ================================================ FILE: src/syntax/test-syntax.rkt ================================================ #lang racket (require "syntax.rkt" "platform.rkt" "types.rkt") (module+ test (require rackunit "../config.rkt" "../syntax/load-platform.rkt") (activate-platform! (*platform-name*)) (define f64 (get-representation 'binary64)) (define sin-proc (impl-info 'sin.f64 'fl)) (check-equal? (sin-proc 0.0) 0.0 "sin(0) = 0") ; get-fpcore-impl (define (get-impl op props itypes) (get-fpcore-impl op props itypes)) (check-equal? (get-impl '+ '((:precision . binary64)) (list f64 f64)) '+.f64) (check-equal? (get-impl '+ '((:precision . binary64) (:description . "test")) (list f64 f64)) '+.f64) (check-equal? (get-impl 'sin '((:precision . binary64)) (list f64)) 'sin.f64) (void)) ================================================ FILE: src/syntax/type-check.rkt ================================================ #lang racket (require "../utils/common.rkt" "../utils/errors.rkt" "types.rkt" "platform.rkt" "syntax.rkt" (except-in "platform-language.rkt" quasisyntax)) (provide assert-program-typed!) (define (repr-description t) (match t [(? representation?) (representation-name t)] [(? array-representation?) `(array ,(repr-description (array-representation-elem t)) ,(array-representation-len t))])) (define (repr-compatible-with-precision? repr precision-repr) (match repr [(? array-representation?) (repr-compatible-with-precision? (array-representation-elem repr) precision-repr)] [(? representation?) (or (equal? (representation-type repr) 'bool) (equal? repr precision-repr))])) (define (array-of elem dims) (for/fold ([out elem]) ([d (in-list (reverse dims))]) (make-array-representation #:elem out #:len d))) (define (assert-program-typed! stx) (define-values (vars props body) (match (syntax-e stx) [(list (app syntax-e 'FPCore) _ (app syntax-e (list vars ...)) props ... body) (values vars props body)] [(list (app syntax-e 'FPCore) (app syntax-e (list vars ...)) props ... body) (values vars props body)])) (define default-dict `((:precision . ,(*default-precision*)))) (define prop-dict (apply dict-set* default-dict (map syntax->datum props))) (define prec (dict-ref prop-dict ':precision)) (define program-repr (get-representation prec)) (define-values (var-names var-types) (for/lists (var-names var-types) ([var (in-list vars)]) (match (syntax->datum var) [(list '! props ... name dims ...) (define prop-dict (props->dict props)) (define arg-prec (dict-ref prop-dict ':precision prec)) (define arg-repr (get-representation arg-prec)) (values name (array-of arg-repr dims))] [(list (? symbol? name) dims ...) (values name (array-of program-repr dims))] [(? symbol? name) (values name program-repr)]))) (define ctx (context var-names program-repr var-types)) (values (assert-expression-type! body prop-dict ctx) ctx)) (define (assert-expression-type! stx props ctx) (define errs '()) (define (error! stx fmt . args) (define args* (map repr-description args)) (set! errs (cons (cons stx (apply format fmt args*)) errs))) (define repr (expression->type stx props ctx error!)) (define expected (context-repr ctx)) (when (not (repr-compatible-with-precision? repr expected)) (error! stx "Expected program of type ~a, got type ~a" (repr-description expected) (repr-description repr))) (unless (null? errs) (raise-herbie-syntax-error "Program has type errors" #:locations errs)) repr) (define (application->string op types) (format "(~a ~a)" op (string-join (for/list ([t types]) (if t (format "<~a>" (repr-description t)) "")) " "))) (define (expression->type stx prop-dict ctx error!) (let loop ([stx stx] [prop-dict prop-dict] [ctx ctx]) (match stx [#`,(? number?) (get-representation (dict-ref prop-dict ':precision))] [#`,(? operator-exists? op) (match (get-fpcore-impl op prop-dict '()) [#f ; no implementation found (error! stx "No implementation of `~a` in platform for context `~a`" op prop-dict) (get-representation (dict-ref prop-dict ':precision))] [impl (impl-info impl 'otype)])] [#`,(? symbol? x) (context-lookup ctx x)] [#`(let ([,ids #,exprs] ...) #,body) (define ctx* (for/fold ([ctx* ctx]) ([id (in-list ids)] [expr (in-list exprs)]) (context-extend ctx* id (loop expr prop-dict ctx)))) (loop body prop-dict ctx*)] [#`(let* ([,ids #,exprs] ...) #,body) (define ctx* (for/fold ([ctx* ctx]) ([id (in-list ids)] [expr (in-list exprs)]) (context-extend ctx* id (loop expr prop-dict ctx*)))) (loop body prop-dict ctx*)] [#`(if #,branch #,ifstmt #,elsestmt) (define cond-ctx (struct-copy context ctx [repr (get-representation 'bool)])) (define cond-repr (loop branch prop-dict cond-ctx)) (unless (equal? (representation-type cond-repr) 'bool) (error! stx "If statement has non-boolean type ~a for branch" (repr-description cond-repr))) (define ift-repr (loop ifstmt prop-dict ctx)) (define iff-repr (loop elsestmt prop-dict ctx)) (unless (equal? ift-repr iff-repr) (error! stx "If statement has different types for if (~a) and else (~a)" (repr-description ift-repr) (repr-description iff-repr))) ift-repr] [#`(! #,props ... #,body) (loop body (apply dict-set prop-dict (map syntax->datum props)) ctx)] [#`(,(? (curry hash-has-key? (*functions*)) fname) #,args ...) ; TODO: inline functions expect uniform types, this is clearly wrong (match-define (list vars prec _) (hash-ref (*functions*) fname)) (define repr (get-representation prec)) (define ireprs (map (lambda (arg) (loop arg prop-dict ctx)) args)) (define expected (map (const repr) vars)) (unless (andmap equal? ireprs expected) (error! stx "Invalid arguments to ~a; expects ~a but got ~a" fname fname (application->string fname expected) (application->string fname ireprs))) repr] [#`(array #,first-elem #,rest-elems ...) (define first-type (loop first-elem prop-dict ctx)) (for ([elem (in-list rest-elems)]) (define t (loop elem prop-dict ctx)) (unless (equal? t first-type) (error! stx "Array elements have mismatched types: ~a vs ~a" (repr-description first-type) (repr-description t)))) (array-of first-type (list (+ 1 (length rest-elems))))] [#`(ref #,arr #,idx) (define arr-type (loop arr prop-dict ctx)) (define raw (syntax-e idx)) (unless (integer? raw) (error! idx "Array index must be a literal integer, got ~a" idx)) (match arr-type [(? array-representation?) (define len (array-representation-len arr-type)) (define elem (array-representation-elem arr-type)) (when (and (integer? raw) (or (< raw 0) (>= raw len))) (error! idx "Array index ~a out of bounds for length ~a" raw len)) elem] [_ (error! stx "ref expects an array, got ~a" (repr-description arr-type)) (get-representation (dict-ref prop-dict ':precision))])] [#`(cast #,arg) (define irepr (loop arg prop-dict ctx)) (define repr (get-representation (dict-ref prop-dict ':precision))) (cond [(equal? irepr repr) repr] [else (match (get-fpcore-impl 'cast prop-dict (list irepr)) [#f ; no implementation found (error! stx "No implementation of `~a` in platform for context `~a`" (application->string 'cast (list irepr)) prop-dict) (get-representation (dict-ref prop-dict ':precision))] [impl (impl-info impl 'otype)])])] [#`(,(? symbol? op) #,args ...) (define ireprs (map (lambda (arg) (loop arg prop-dict ctx)) args)) (match (get-fpcore-impl op prop-dict ireprs) [#f ; no implementation found (error! stx "No implementation of `~a` in platform for context `~a`" (application->string op ireprs) prop-dict) (get-representation (dict-ref prop-dict ':precision))] [impl (impl-info impl 'otype)])]))) (module+ test (require rackunit) (require "platform.rkt" "../syntax/load-platform.rkt") (activate-platform! (*platform-name*)) ;; Dummy representation registration (check-false (hash-has-key? (platform-representations (*active-platform*)) 'dummy)) (define pf (struct-copy $platform (*active-platform*) [representations (hash-copy (platform-representations (*active-platform*)))] [implementations (hash-copy (platform-implementations (*active-platform*)))] [representation-costs (hash-copy (platform-representation-costs (*active-platform*)))])) (parameterize ([*active-platform* pf]) (define dummy-repr (make-representation #:name 'dummy #:bf->repr identity #:repr->bf identity #:ordinal->repr identity #:repr->ordinal identity #:total-bits 0 #:special-value? (const #f))) (hash-set! (platform-representations pf) 'dummy dummy-repr) (hash-set! (platform-representation-costs pf) 'dummy 1) (check-true (hash-has-key? (platform-representations pf) 'dummy)) (define dummy (get-representation 'dummy)) (check-equal? (representation-name dummy) 'dummy) (check-equal? (get-representation 'dummy) dummy) ;; Context operations (define (get-representation 'binary64)) (define (get-representation 'bool)) (define ctx (context '() '())) (define ctx1 (context-extend ctx 'x )) (check-equal? (context-vars ctx1) '(x)) (check-equal? (context-lookup ctx1 'x) ) (define ctx2 (context-extend ctx1 'y )) (check-equal? (context-vars ctx2) '(y x)) (check-equal? (context-lookup ctx2 'y) ) (check-equal? (context-lookup ctx2 'x) ) (define (fail! stx msg . args) (error (apply format msg args) stx)) (define (check-types env-type rtype expr #:env [env '()]) (define ctx (context (map car env) env-type (map cdr env))) (define repr (expression->type expr (repr->prop env-type) ctx fail!)) (cond [(and (representation? repr) (representation? rtype)) (check-equal? (representation-name repr) (representation-name rtype))] [else (check-equal? repr rtype)])) (define (check-fails type expr #:env [env '()]) (define fail? #f) (define ctx (context (map car env) type (map cdr env))) (expression->type expr (repr->prop type) ctx (lambda _ (set! fail? #t))) (check-true fail?)) (check-types #'4) (check-types #'x #:env `((x . ,))) (check-types #'(acos x) #:env `((x . ,))) (check-fails #'(acos x) #:env `((x . ,))) (check-types #'(and a b) #:env `((a . ,) (b . ,))) (check-types #'(if (== a 1) 1 0) #:env `((a . ,))) (check-fails #'(if (== a 1) 1 0) #:env `((a . ,))) (check-types #'(let ([a 1]) TRUE)) (check-fails #'(if (== a 1) 1 TRUE) #:env `((a . ,))) (check-types #'(let ([a 1]) a) #:env `((a . ,))) ;; Array-aware typing (define vec-type (array-of '(2))) (define vec3-type (array-of '(3))) (check-types vec-type #'(array 1 2)) (check-types vec3-type #'(array 1 2 3)) (check-fails #'(array (array 1) (array 1 2))) (check-types #'(ref (array 5 6) 0)) (check-types #'(ref A 2) #:env `((A . ,vec3-type))) (check-fails #'(ref A 3) #:env `((A . ,vec3-type))) (check-fails #'(ref x 0) #:env `((x . ,)))) (check-exn exn:fail? (lambda () (assert-program-typed! #'(FPCore () :precision (array binary64 2) (array 1.0 2.0))))) (check-not-exn (lambda () (assert-program-typed! #'(FPCore () :precision binary64 (array 1.0 2.0))))) (check-not-exn (lambda () (assert-program-typed! #'(FPCore ((v 3)) :precision binary64 (ref v 2))))) (check-not-exn (lambda () (assert-program-typed! #'(FPCore ((a 3) (b 3)) :precision binary64 (+ (+ (ref a 0) (ref b 0)) (+ (ref a 2) (ref b 2)))))))) ================================================ FILE: src/syntax/types.rkt ================================================ #lang racket (require math/bigfloat math/base math/flonum "../utils/errors.rkt") (provide (struct-out representation) (struct-out array-representation) repr->prop array-representation-base array-representation-shape shift unshift (struct-out context) *context* context-extend context-lookup make-representation make-array-representation) ;; Representations (struct representation (name type bf->repr repr->bf ordinal->repr repr->ordinal total-bits special-value?) #:transparent #:methods gen:custom-write [(define (write-proc repr port mode) (fprintf port "#" (representation-name repr)))]) (struct array-representation representation (elem len) #:transparent) (define (array-representation-base repr) (if (array-representation? repr) (array-representation-base (array-representation-elem repr)) repr)) (define (array-representation-shape repr) (if (array-representation? repr) (cons (array-representation-len repr) (array-representation-shape (array-representation-elem repr))) '())) ;; Converts a representation into a rounding property (define (repr->prop repr) (match repr [(? array-representation?) (repr->prop (array-representation-elem repr))] [(? representation?) (match (representation-type repr) ['bool '()] ['real (list (cons ':precision (representation-name repr)))])])) (define (make-representation #:name name #:bf->repr bf->repr #:repr->bf repr->bf #:ordinal->repr ordinal->repr #:repr->ordinal repr->ordinal #:total-bits total-bits #:special-value? special-value?) (representation name 'real bf->repr repr->bf ordinal->repr repr->ordinal total-bits special-value?)) (define (make-array-representation #:elem elem-repr #:len len) (unless (exact-positive-integer? len) (raise-herbie-error "Arrays require a positive length, got ~a" len)) (define array-ty `(array ,(representation-type elem-repr) ,len)) (define name (string->symbol (format "array~a-~a" (representation-name elem-repr) len))) ;; TODO: Array representations currently inherit scalar conversion slots. ;; These should not be called for arrays; we'll clean up the hierarchy later. (define total-bits (* len (representation-total-bits elem-repr))) (array-representation name array-ty void void void void total-bits void elem-repr len)) (module hairy racket/base (require (only-in math/private/bigfloat/mpfr get-mpfr-fun _mpfr-pointer _rnd_t bf-rounding-mode)) (require ffi/unsafe) (provide bigfloat->float32) (define mpfr-get-flt (get-mpfr-fun 'mpfr_get_flt (_fun _mpfr-pointer _rnd_t -> _float))) (define (bigfloat->float32 x) (mpfr-get-flt x (bf-rounding-mode)))) (require (submod "." hairy)) (define (float32->bit-field x) (integer-bytes->integer (real->floating-point-bytes x 4) #f #f)) (define (float32->ordinal x) (if (negative? x) (- (float32->bit-field (- x))) (float32->bit-field (abs x)))) (define (bit-field->float32 x) (floating-point-bytes->real (integer->integer-bytes x 4 #f #f) #f)) (define (ordinal->float32 x) (if (negative? x) (- (bit-field->float32 (- x))) (bit-field->float32 x))) (define (shift bits fn) (define shift-val (expt 2 bits)) (λ (x) (fn (- x shift-val)))) (define (unshift bits fn) (define shift-val (expt 2 bits)) (λ (x) (+ (fn x) shift-val))) ;; Does not use make-representation to define a repr of bool (define (representation 'bool 'bool identity identity (curry = 0) (lambda (x) (if x 0 -1)) 1 (const #f))) (define (make-representation #:name 'binary32 #:bf->repr bigfloat->float32 #:repr->bf (lambda (x) (parameterize ([bf-precision 24]) (bf x))) #:ordinal->repr ordinal->float32 #:repr->ordinal float32->ordinal #:total-bits 32 #:special-value? nan?)) (define (make-representation #:name 'binary64 #:bf->repr bigfloat->flonum #:repr->bf (lambda (x) (parameterize ([bf-precision 53]) (bf x))) #:ordinal->repr ordinal->flonum #:repr->ordinal flonum->ordinal #:total-bits 64 #:special-value? nan?)) ;; Contexts (struct context (vars repr var-reprs) #:transparent) ;; Current context (define *context* (make-parameter #f)) (define (context-extend ctx var repr) (struct-copy context ctx [vars (cons var (context-vars ctx))] [var-reprs (cons repr (context-var-reprs ctx))])) (define (context-lookup ctx var) (dict-ref (map cons (context-vars ctx) (context-var-reprs ctx)) var)) ================================================ FILE: src/utils/common.rkt ================================================ #lang racket (require math/base "../config.rkt") (provide reap drop-at find-duplicates partial-sums get-seed set-seed! quasisyntax sym-append format-time format-bits prop-dict/c props->dict fpcore->string (all-from-out "../config.rkt")) (module+ test (require rackunit)) ;; Various syntactic forms of convenience used in Herbie (define-syntax-rule (reap [sows ...] body ...) (let* ([sows (let ([store '()]) (cons (λ () store) (match-lambda* [(list elt) (set! store (cons elt store))] [(list) store] [_ (error 'reap "invalid sow")])))] ...) (let ([sows (cdr sows)] ...) body ...) (values (reverse ((car sows))) ...))) (define (drop-at ls index) (define-values (front back) (split-at ls index)) (append front (rest back))) (define (partial-sums vec) (define res (make-vector (vector-length vec))) (for/fold ([cur-psum 0]) ([(el idx) (in-indexed (in-vector vec))]) (define new-psum (+ cur-psum el)) (vector-set! res idx new-psum) new-psum) res) (module+ test (check-equal? (partial-sums #(1 4 6 3 8)) #(1 5 11 14 22))) (define (find-duplicates l) (map car (filter (compose pair? rest) (group-by identity l)))) ;; 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)]))) ;; String formatting operations (define (format-time ms) (cond [(< ms 1000) (format "~ams" (round ms))] [(< ms 60000) (format "~as" (/ (round (/ ms 100.0)) 10))] [(< ms 3600000) (format "~amin" (/ (round (/ ms 6000.0)) 10))] [else (format "~ahr" (/ (round (/ ms 360000.0)) 10))])) (module+ test (check-equal? (format-time 60000) "1.0min") (check-equal? (format-time 3600000) "1.0hr") (check-equal? (format-time 500) "500ms") (check-equal? (format-time 2000) "2.0s") (check-equal? (format-time 0) "0ms") (check-equal? (format-time 1800000) "30.0min") (check-equal? (format-time 7200000) "2.0hr")) (define (format-bits r #:unit [unit? #f]) (define unit (if unit? "b" "")) (cond [(not r) ""] [else (format "~a~a" (/ (round (* r 10)) 10) unit)])) ;; Symbol generation (define (sym-append . args) (string->symbol (apply string-append (map ~a args)))) ;; FPCore properties (define prop-dict/c (listof (cons/c symbol? any/c))) ;; Prop list to dict (define (props->dict props) (let loop ([props props] [dict '()]) (match props [(list key val rest ...) (loop rest (dict-set dict key val))] [(list key) (error 'props->dict "unmatched key" key)] [(list) dict]))) (define (fpcore->string core) (define-values (ident args props expr) (match core [(list 'FPCore name (list args ...) props ... expr) (values name args props expr)] [(list 'FPCore (list args ...) props ... expr) (values #f args props expr)])) (define props* ; make sure each property (name, value) gets put on the same line (for/list ([(prop name) (in-dict (props->dict props))]) (format "~a ~a" prop (pretty-format name (- 69 (string-length (~a prop))) #:mode 'write)))) (define top (if ident (format "FPCore ~a ~a" ident args) (format "FPCore ~a" args))) (format "(~a\n ~a\n ~a)" top (string-join props* "\n ") (pretty-format expr 70 #:mode 'write))) ================================================ FILE: src/utils/dvector.rkt ================================================ #lang racket (provide make-dvector dvector-add! dvector-set! dvector-ref in-dvector dvector-length dvector-capacity create-dvector dvector->vector) (define starting-capacity 128) (struct dvector ([vec #:mutable] [length #:mutable] filling-value) #:property prop:equal+hash (list (λ (a b eq?) ; equal? override (and (dvector? b) (eq? (dvector-vec a) (dvector-vec b)))) (λ (a hc) ; hash-code (+ (hc (dvector-vec a)) (dvector-length a))) (λ (a hc) ; secondary-hash-code (+ (hc (dvector-vec a)) (* 7 (+ 1 (dvector-length a))))))) (define (dvector->vector dvec) (vector-copy (dvector-vec dvec) 0 (dvector-length dvec))) (define (make-dvector [size starting-capacity] [v #f]) (define size* (cond [(> size starting-capacity) (let loop ([size* (* starting-capacity 2)]) (if (< size size*) size* (loop (* size* 2))))] [(zero? size) starting-capacity] ;; zero capacity is not allowed as it is going to break dvector-extend! [else size])) ;; if user has a specific capacity in mind - do not go beyond that (dvector (make-vector size* v) 0 v)) (define (create-dvector . args) (define dvec (make-dvector)) (for ([arg args]) (dvector-add! dvec arg)) dvec) (define (dvector-capacity dvec) (vector-length (dvector-vec dvec))) (define (dvector-extend! dvec) (match-define (dvector vec _ filling-val) dvec) (define cap (dvector-capacity dvec)) (define vec* (make-vector (* 2 cap) filling-val)) (vector-copy! vec* 0 vec) (set-dvector-vec! dvec vec*)) (define (dvector-add! dvec elem) (match-define (dvector vec len _) dvec) (cond [(equal? len (dvector-capacity dvec)) (dvector-extend! dvec) (dvector-add! dvec elem)] [else (vector-set! vec len elem) (set-dvector-length! dvec (add1 len)) len])) (define (dvector-set! dvec idx elem) (match-define (dvector vec len _) dvec) (cond [(>= idx (dvector-capacity dvec)) (dvector-extend! dvec) (dvector-set! dvec idx elem)] [else (vector-set! vec idx elem) (set-dvector-length! dvec (max len (add1 idx)))])) (define (dvector-ref dvec idx) (vector-ref (dvector-vec dvec) idx)) (define (in-dvector dvec [start 0] [end (dvector-length dvec)] [step 1]) (when (or (< (dvector-length dvec) (or end (dvector-length dvec))) (> start (dvector-length dvec))) (error "in-dvector is out of index")) (in-vector (dvector-vec dvec) start (or end (dvector-length dvec)) step)) (module+ test (require rackunit) ;; Test: Create a new dvector (define dv (make-dvector)) (define dv1 (make-dvector)) ; default (check-equal? (dvector-length dv1) 0) (check-equal? (dvector-capacity dv1) starting-capacity) (check-equal? (vector-length (dvector-vec dv1)) starting-capacity) (define dv2 (make-dvector starting-capacity 5)) (check-equal? (dvector-length dv2) 0) (check-equal? (dvector-capacity dv2) starting-capacity) (check-equal? (vector-length (dvector-vec dv2)) starting-capacity) (check-equal? (vector-ref (dvector-vec dv2) 0) 5) ;; Large input triggers dynamic size growth (define dv3 (make-dvector 300)) (check-true (> (dvector-capacity dv3) 300)) ;; Custom equal? behavior: only same vector => equal (define dv4 dv2) ; same instance (define dv5 (make-dvector 10 5)) ; same content, different vector (check-true (equal? dv2 dv4)) (check-equal? (dvector-length dv) 0) (check-equal? (vector-length (dvector-vec dv)) 128) ;; Test: Adding one element (dvector-add! dv 'a) (check-equal? (dvector-length dv) 1) (check-equal? (dvector-ref dv 0) 'a) ;; Test: Adding multiple elements within capacity (for ([i (in-range 1 128)]) (dvector-add! dv i)) (check-equal? (dvector-length dv) 128) (check-equal? (dvector-ref dv 1) 1) (check-equal? (dvector-ref dv 127) 127) ;; Test: Adding element to trigger extension (dvector-add! dv 'extended) (check-equal? (dvector-length dv) 129) (check-equal? (vector-length (dvector-vec dv)) 256) ; Should have doubled (dvector-add! dv 'new) (check-equal? (dvector-length dv) 130) (check-equal? (dvector-ref dv 129) 'new) ;; Test: in-dvector (implicit end) (define values (for/list ([x (in-dvector dv)]) x)) (check-equal? (length values) 130) (check-equal? (first values) 'a) (check-equal? (last values) 'new) ;; Test: in-dvector with range and step (define stepped (for/list ([x (in-dvector dv 0 10 2)]) x)) (check-equal? stepped (list 'a 2 4 6 8)) ;; Test: Reference out of bounds (check-exn exn:fail? (lambda () (dvector-ref dv 999))) ;; Test: Length after many additions (for ([i (in-range 200)]) (dvector-add! dv i)) (check-equal? (dvector-length dv) 330) (check-equal? (vector-length (dvector-vec dv)) 512)) ; Should have extended again ================================================ FILE: src/utils/errors.rkt ================================================ #lang racket (require "../config.rkt") (provide raise-herbie-error raise-herbie-syntax-error raise-herbie-sampling-error raise-herbie-missing-error exception->datum herbie-error->string (struct-out exn:fail:user:herbie) (struct-out exn:fail:user:herbie:sampling) (struct-out exn:fail:user:herbie:missing) warn warning-log) (struct exn:fail:user:herbie exn:fail:user (url) #:extra-constructor-name make-exn:fail:user:herbie) (struct exn:fail:user:herbie:syntax exn:fail:user:herbie (locations) #:extra-constructor-name make-exn:fail:user:herbie:syntax) (struct exn:fail:user:herbie:sampling exn:fail:user:herbie () #:extra-constructor-name make-exn:fail:user:herbie:sampling) (struct exn:fail:user:herbie:missing exn:fail:user:herbie () #:extra-constructor-name make-exn:fail:user:herbie:missing) (define (raise-herbie-error message #:url [url #f] . args) (raise (make-exn:fail:user:herbie (apply format message args) (current-continuation-marks) url))) (define (raise-herbie-syntax-error message #:url [url "faq.html#invalid-syntax"] #:locations [locations '()] . args) (raise (make-exn:fail:user:herbie:syntax (apply format message args) (current-continuation-marks) url locations))) (define (raise-herbie-sampling-error message #:url [url #f] . args) (raise (make-exn:fail:user:herbie:sampling (apply format message args) (current-continuation-marks) url))) (define (raise-herbie-missing-error message #:url [url #f] . args) (raise (make-exn:fail:user:herbie:missing (apply format message args) (current-continuation-marks) url))) (define (herbie-error-url exn) (format "https://herbie.uwplse.org/doc/~a/~a" *herbie-version* (exn:fail:user:herbie-url exn))) (define (syntax->error-format-string stx) (define file (cond [(path? (syntax-source stx)) (define-values (base name dir?) (split-path (syntax-source stx))) (path->string name)] [else (syntax-source stx)])) (format "~a:~a:~a: ~~a" file (or (syntax-line stx) "") (or (syntax-column stx) (syntax-position stx)))) (define (traceback->datum exn) (define ctx (continuation-mark-set->context (exn-continuation-marks exn))) (for/list ([(name loc) (in-dict ctx)]) (define name* (or name "(unnamed)")) (match loc [(srcloc file line col _ _) (list name* file line col)] [#f (cons name* #f)]))) (define (syntax-locations->datum exn) (for/list ([(stx msg) (in-dict (exn:fail:user:herbie:syntax-locations exn))]) (list msg (syntax-source stx) (syntax-line stx) (syntax-column stx) (syntax-position stx)))) (define (exception->datum exn) (match exn [(? exn:fail:user:herbie:missing?) (list 'exn 'missing (exn-message exn) (herbie-error-url exn) #f (traceback->datum exn))] [(? exn:fail:user:herbie:sampling?) (list 'exn 'sampling (exn-message exn) (herbie-error-url exn) #f (traceback->datum exn))] [(? exn:fail:user:herbie:syntax?) (list 'exn 'syntax (exn-message exn) (herbie-error-url exn) (syntax-locations->datum exn) (traceback->datum exn))] [(? exn:fail:user:herbie?) (list 'exn 'herbie (exn-message exn) (herbie-error-url exn) '() (traceback->datum exn))] [(? exn?) (list 'exn #f (exn-message exn) #f '() (traceback->datum exn))])) (define (herbie-error->string err) (call-with-output-string (λ (p) (match err [(exn:fail:user:herbie:syntax message marks url locations) (fprintf p "~a\n" message) (for ([(stx message) (in-dict locations)]) (fprintf p " ~a\n" (format (syntax->error-format-string stx) message))) (when url (fprintf p "See for more.\n" *herbie-version* url))] [(exn:fail:user:herbie message marks url) (fprintf p "~a\n" message) (when url (fprintf p "See for more.\n" *herbie-version* url))])))) (define old-error-display-handler (error-display-handler)) (error-display-handler (λ (message err) (cond [(exn:fail:user:herbie? err) (display (herbie-error->string err) (current-error-port))] [(exn:fail:read? err) (printf "Invalid syntax\n ~a\nSee for more.\n" (exn-message err) *herbie-version*)] [else (old-error-display-handler message err)]))) (define/reset warnings (mutable-set)) (define/reset warning-log '()) (define (warn type message #:url [url #f] #:extra [extra '()] . args) (unless (set-member? (warnings) type) (eprintf "Warning: ~a\n" (apply format message args)) (for ([line extra]) (eprintf " ~a\n" line)) (define url* (and url (format "https://herbie.uwplse.org/doc/~a/~a" *herbie-version* url))) (when url* (eprintf "See <~a> for more.\n" url*)) (define entry (list (~a type) (apply format message args) url* extra)) (set-add! (warnings) type) (warning-log (cons entry (warning-log))))) ================================================ FILE: src/utils/multi-command-line.rkt ================================================ #lang racket (require (for-syntax syntax/parse)) (provide multi-command-line) (define-syntax (multi-command-line stx) (syntax-parse stx [(_ #:program big-name #:version version args ... #:subcommands [name:id help:str subargs ...] ... #:args else-subargs body) #`(let ([true-name big-name]) (command-line #:program true-name #:multi [("-v" "--version") ("Print the version and exit") (printf "~a\n" version) (exit)] #:usage-help "This command has subcommands:" #,@(for/list ([name (syntax->list #'(name ...))] [help (syntax->list #'(help ...))]) (datum->syntax name (format " ~a:\t~a" (syntax->datum name) (syntax->datum help)))) "Learn more about a subcommand with --help" #:args cmdline-args (match cmdline-args [(cons (== (~a 'name)) rest) (multi-command-line #:program (format "~a ~a" true-name 'name) #:argv rest args ... subargs ...)] ... [fallthrough (apply (λ else-subargs body) fallthrough)])))] [(_ args ...) #'(command-line args ...)])) ================================================ FILE: src/utils/pareto.rkt ================================================ #lang racket (provide (struct-out pareto-point) pareto-map pareto-union pareto-combine) (struct pareto-point (cost error data) #:prefab) (define (ppt->pt ppt) (list (pareto-point-cost ppt) (pareto-point-error ppt))) (define (pt->ppt pt) (pareto-point (first pt) (second pt) (list))) (define (pareto-shift ppt0 frontier) (match-define (pareto-point cost0 err0 _) ppt0) (for/list ([ppt (in-list frontier)]) (match-define (pareto-point cost err _) ppt) (pareto-point (+ cost0 cost) (+ err0 err) (list)))) (define (pareto-compare pt1 pt2) (match-define (pareto-point cost1 err1 data1) pt1) (match-define (pareto-point cost2 err2 data2) pt2) (cond [(and (= cost1 cost2) (= err1 err2)) '=] [(and (<= cost1 cost2) (<= err1 err2)) '<] [(and (>= cost1 cost2) (>= err1 err2)) '>] [else '<>])) (define (pareto-map f curve) (for/list ([ppt (in-list curve)]) (struct-copy pareto-point ppt [data (f (pareto-point-data ppt))]))) ;; Takes two lists of `pareto-point` structs that are Pareto-optimal ;; and returns the Pareto-optimal subset of their union. ;; The curves most be sorted using the same method. (define (pareto-union curve1 curve2 #:combine [combine (lambda (a b) (append a b))]) (let loop ([curve1 curve1] [curve2 curve2]) ; The curve is sorted so that highest accuracy is first (match* (curve1 curve2) [('() _) curve2] [(_ '()) curve1] [((cons ppt1 rest1) (cons ppt2 rest2)) (match (pareto-compare ppt1 ppt2) ['< (loop curve1 rest2)] ['> (loop rest1 curve2)] ['= (define joint-data (combine (pareto-point-data ppt1) (pareto-point-data ppt2))) (define joint (struct-copy pareto-point ppt1 [data joint-data])) (cons joint (loop rest1 rest2))] ['<> (if (< (pareto-point-error ppt1) (pareto-point-error ppt2)) (cons ppt1 (loop rest1 curve2)) (cons ppt2 (loop curve1 rest2)))])]))) ;; Takes a Pareto frontier and returns the subset of ;; points that are convex. (define (pareto-convex ppts) (let loop ([ppts* '()] [ppts ppts]) (match ppts [(list p0 p1 p2 pns ...) (match-define (pareto-point p0x p0y _) p0) (match-define (pareto-point p1x p1y _) p1) (match-define (pareto-point p2x p2y _) p2) ; if { p0, p1, p2 } are not convex: ; discard p1 ; try backtracking one point (if not continue) ; else move forward one point (define m01 (/ (- p1y p0y) (- p1x p0x))) (define m12 (/ (- p2y p1y) (- p2x p1x))) (match* ((> m12 m01) (null? ppts*)) [(#t #t) (loop ppts* (append (list p0 p2) pns))] [(#t #f) (loop (rest ppts*) (append (list (first ppts*) p0 p2) pns))] [(#f _) (loop (cons p0 ppts*) (append (list p1 p2) pns))])] [_ (append (reverse ppts*) ppts)]))) ;; Takes a list of `pareto-point` structs ;; and returns the Pareto-optimal subset. (define (pareto-minimize ppts) (define ppts* (sort ppts < #:key pareto-point-cost)) (for/fold ([minimized '()]) ([ppt (in-list ppts*)]) (pareto-union (list ppt) minimized))) ;; Creates a synthetic frontier from multiple frontiers ;; as described in the ARITH '21 paper. (define (pareto-combine frontiers #:convex? [convex? #f]) (define (finalize f) (if convex? (pareto-convex f) f)) (define frontiers* (map (λ (f) (pareto-minimize (map pt->ppt f))) frontiers)) (for/fold ([combined (list)] #:result (map ppt->pt combined)) ([frontier (in-list frontiers*)]) (if (null? combined) (finalize frontier) (for/fold ([combined* (list)] #:result (finalize combined*)) ([ppt (in-list combined)]) (define ppts (pareto-minimize (pareto-shift ppt frontier))) (pareto-union ppts combined*))))) (module+ test (require rackunit) (define (make-pareto pts) (sort (for/list ([pt (in-list pts)]) (match-define (list cost err altns ...) pt) (pareto-point cost err altns)) < #:key pareto-point-error)) (define (from-pareto pts) (sort (for/list ([ppt (in-list pts)]) (match-define (pareto-point cost err altns) ppt) (list* cost err altns)) < #:key first)) (define (pareto-add curve d c e) (pareto-union (list (pareto-point c e (list d))) curve)) (check-equal? (from-pareto (make-pareto '((1 5 a) (2 3 b) (5 1 a b)))) '((1 5 a) (2 3 b) (5 1 a b))) (check-equal? (from-pareto (pareto-add (make-pareto '()) 'a 1 5)) '((1 5 a))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 5 a) (5 1 b))) 'c 3 3)) '((1 5 a) (3 3 c) (5 1 b))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 5 a) (3 3 b))) 'c 5 1)) '((1 5 a) (3 3 b) (5 1 c))) (check-equal? (from-pareto (pareto-add (make-pareto '((3 3 b) (5 1 c))) 'a 1 5)) '((1 5 a) (3 3 b) (5 1 c))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 5 a) (3 3 b) (5 1 c))) 'd 1 5)) '((1 5 d a) (3 3 b) (5 1 c))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 5 a) (3 3 b) (5 1 c))) 'd 3 3)) '((1 5 a) (3 3 d b) (5 1 c))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 5 a) (3 3 b) (5 1 c))) 'd 2 2)) '((1 5 a) (2 2 d) (5 1 c))) (check-equal? (from-pareto (pareto-add (make-pareto '((1 1 a))) 'b 1 3)) '((1 1 a)))) ================================================ FILE: src/utils/pretty-print.rkt ================================================ #lang racket (require math/bigfloat) (provide bigfloat-interval-shortest bigfloat-pick-point) (define (bigfloat->normal-string x) (cond [(bfzero? x) (match (bigfloat-signbit x) [0 '+zero] [1 '-zero])] [(bfinfinite? x) (match (bigfloat-signbit x) [0 '+inf] [1 '-inf])] [(bfnan? x) 'nan] [else (define s (bigfloat->string x)) (define-values (sign s-abs) (if (string-prefix? s "-") (values '- (substring s 1)) (values '+ s))) (define-values (mantissa e) (match (string-split s-abs "e" #:trim? #f) [(list m e) (values m (string->number e))] [(list m) (values m 0)])) (define-values (pre-dot post-dot e*) (match (string-split mantissa "." #:trim? #f) [(list pre) #:when (= (string-length pre) 1) (values pre "0" e)] [(list pre) (values (substring pre 0 1) (substring pre 1) (- (string-length pre) 1))] [(list "0" s) (let loop ([idx 0] [e e]) (if (eq? (string-ref s idx) #\0) (loop (+ idx 1) (- e 1)) (values (substring s idx (+ idx 1)) (substring s (+ idx 1)) (- e 1))))] [(list pre post) (if (= (string-length pre) 1) (values pre post e) (values (substring pre 0 1) (string-append (substring pre 1) post) (+ (string-length pre) -1 e)))])) (list sign (string->number pre-dot) post-dot e*)])) (module+ test (require rackunit) (check-equal? (bigfloat->normal-string (bf -1e-100)) '(- 1 "000000000000000019991899802602883619648" -100)) (check-equal? (bigfloat->normal-string (bf "-1")) '(- 1 "0" 0)) (check-equal? (bigfloat->normal-string (bf "-.1")) '(- 1 "000000000000000000000000000000000000001" -1)) (check-equal? (bigfloat->normal-string (bf "-.000001")) '(- 1 "000000000000000000000000000000000000001" -6)) (check-equal? (bigfloat->normal-string (bf "0")) '+zero) (check-equal? (bigfloat->normal-string (bf "-1964363925810.15")) '(- 1 "964363925810149999999999999999999999999" 12)) (check-equal? (bigfloat->normal-string (bf "-3.1014574914586375e-17")) '(- 3 "101457491458637500000000000000000000006" -17))) (define (digit-interval-shortest a b) (define digits '(0 5 2 4 6 8 1 3 7 9)) (for/first ([d digits] #:when (<= a d b)) d)) (define (string-interval-shortest a b) (let loop ([idx 0]) (cond [(>= idx (string-length a)) a] [(eq? (string-ref b idx) (string-ref a idx)) (loop (+ idx 1))] [else (format "~a~a~a" (substring b 0 idx) (let ([x-digit (string->number (substring a idx (+ idx 1)))] [y-digit (string->number (substring b idx (+ idx 1)))]) (digit-interval-shortest (+ x-digit 1) y-digit)) (build-string (- (string-length b) idx 1) (const #\0)))]))) (define (string-pad s n c) (define k (- n (string-length s))) (if (positive? k) (string-append (build-string k (const c)) s) s)) (module+ main (require rackunit) (check string=? (string-pad "1" 2 #\0) "01")) (define (integer-interval-shortest a b) (define sa (number->string a)) (define sb (number->string b)) (cond [(<= a 0 b) 0] [(negative? b) (- (integer-interval-shortest (- b) (- a)))] [else (define s1 (string-pad sa (max (string-length sa) (string-length sb)) #\0)) (define s2 (string-pad sb (max (string-length sa) (string-length sb)) #\0)) (string->number (string-interval-shortest s1 s2))])) (define/contract (bigfloat-interval-shortest x y) (->i ([x bigfloat?] [y bigfloat?]) #:pre (x y) (or (bf<= x y) (bfnan? y)) [result bigfloat?]) (define x-parts (bigfloat->normal-string x)) (define y-parts (bigfloat->normal-string y)) (cond [(bf= x y) y] [(symbol? x-parts) x] [(symbol? y-parts) y] [(bfnegative? y) (bf- (bigfloat-interval-shortest (bf- y) (bf- x)))] [(bfnegative? x) 0.bf] [else (match-define (list x-sign x-pre x-post x-e) x-parts) (match-define (list y-sign y-pre y-post y-e) y-parts) (cond [(>= y-e (+ x-e 1)) (bf (format "1.0e~a" (integer-interval-shortest (+ x-e 1) y-e)))] [(>= y-pre (+ x-pre 1)) (bf (format "~a.0e~a" (digit-interval-shortest (+ x-pre 1) y-pre) y-e))] [else (bf (format "~a.~ae~a" y-pre (string-interval-shortest x-post y-post) y-e))])])) ;; a little more rigorous than it sounds: ;; finds the shortest number `x` near `p1` such that ;; `x1` is in `[p1, p2]` and is no larger than ;; - if `p1` is negative, `p1 / 2` ;; - if `p1` is positive, `p1 * 2` (define/contract (bigfloat-pick-point left right) (->i ([x bigfloat?] [y bigfloat?]) #:pre (x y) (or (bf<= x y) (bfnan? y)) [result bigfloat?]) (cond [(and (bfnegative? left) (bfnegative? right)) (bf- (bigfloat-pick-point (bf- right) (bf- left)))] [(and (bfpositive? left) (bfpositive? right)) (bigfloat-interval-shortest left (bfmin (bf* left 2.bf) right))] [else (bigfloat-interval-shortest left right)])) (module+ test (require math/base) (define (sample-bigfloat) (define exponent (random -1023 1023)) ; Pretend-double (define significand (bf (random-bits (bf-precision)) (- (bf-precision)))) (define val (bfshift (bf+ 1.bf significand) exponent)) (if (= (random 0 2) 1) (bf- val) val)) (for ([i (in-range 10000)]) (define x (sample-bigfloat)) (define y (sample-bigfloat)) (define-values (x* y*) (if (bf< x y) (values x y) (values y x))) (define z (bigfloat-interval-shortest x* y*)) (with-check-info (['x x*] ['z z] ['y y*]) (check bf<= x* z) (check bf<= z y*)))) ================================================ FILE: src/utils/profile.rkt ================================================ #lang racket (require profile/sampler profile/analyzer setup/dirs) (provide profile-merge profile->json json->profile profile-thunk) ;; One annoyance with profiling in Racket is that if function f calls ;; (map g ...), we'll get profile edges (f, map) and (map, g). So if ;; you want to know what "f" spends its time doing, you won't know for ;; sure (imagine it calls map more than once), and if you want to know ;; what "g" is called by, good luck. ;; ;; We thus apply some clever filtering to stacks: if the stack is (f ;; map g), we'll create the edge (f, g), but if it's (f map) we'll ;; still do the edge (f, map) to record the overhead of map. ;; The specific test is whether or not it's part of the Racket ;; standard library (called the "collects") (define collects-dir (let ([dir (find-collects-dir)]) (and dir (path->string dir)))) (define (profile-focus? id src) (define path (and src (srcloc-source src))) (cond [(not src) #t] [(not collects-dir) #t] [(path? path) (not (string-prefix? (path->string path) collects-dir))] [(string? path) (not (string-prefix? path "..."))] ; abbreviated collects paths [else #t])) ;; Filter a stack for edge computation. Keep non-focused functions at the ;; front (self position), then filter them out once we hit a focused function. (define (filter-stack focus? stack) (let loop ([stack stack] [filtering? #f]) (cond [(null? stack) '()] [else (define entry (car stack)) (define focused? (focus? (car entry) (cdr entry))) (cond [focused? (cons entry (loop (cdr stack) #t))] [filtering? (loop (cdr stack) #t)] [else (cons entry (loop (cdr stack) #f))])]))) ;; Filter all stacks in samples using the focus predicate (define (filter-samples focus? cpu-time+samples) (define cpu-time (car cpu-time+samples)) (define samples (cdr cpu-time+samples)) (cons cpu-time (for/list ([sample (in-list samples)]) (define thread-id (car sample)) (define thread-time (cadr sample)) (define stack (cddr sample)) (list* thread-id thread-time (filter-stack focus? stack))))) (define (profile-thunk thunk renderer #:delay [delay-secs 0.05]) (define sampler (create-sampler (current-thread) delay-secs)) (define result (with-handlers ([void (λ (e) (eprintf "profiled thunk error: ~a\n" (if (exn? e) (exn-message e) (format "~e" e))))]) (thunk))) (sampler 'stop) (define raw-samples (sampler 'get-snapshots)) (define filtered-samples (filter-samples profile-focus? raw-samples)) (renderer (analyze-samples filtered-samples)) result) (define (profile-merge . ps) (define nodes (make-hash)) (define root-node (node #f #f '() 0 0 '() '())) (hash-set! nodes (cons #f #f) root-node) (for* ([p (in-list ps)] [n (profile-nodes p)]) (hash-set! nodes (node-loc n) (node (node-id n) (node-src n) '() 0 0 '() '()))) (for* ([p ps] [node (profile-nodes p)]) (profile-add nodes node)) (for ([p ps]) (profile-add nodes (profile-*-node p))) (hash-remove! nodes (cons #f #f)) (profile (apply + (map profile-total-time ps)) (apply + (map profile-cpu-time ps)) (apply + (map profile-sample-number ps)) (apply merge-thread-times (map profile-thread-times ps)) (hash-values nodes) root-node)) (define (translate table node) (hash-ref table (node-loc node))) (define (profile-add table node) (define node* (translate table node)) (set-node-thread-ids! node* (set-union (node-thread-ids node*) (node-thread-ids node))) (set-node-total! node* (+ (node-total node*) (node-total node))) (set-node-self! node* (+ (node-self node*) (node-self node))) (for ([e (node-callers node)]) (define caller* (translate table (edge-caller e))) (match (findf (λ (e2) (eq? (edge-caller e2) caller*)) (node-callers node*)) [#f (define e* (struct-copy edge e [caller caller*] [callee node*])) (set-node-callers! node* (cons e* (node-callers node*)))] [e* (set-edge-total! e* (+ (edge-total e) (edge-total e*))) (set-edge-caller-time! e* (+ (edge-caller-time e) (edge-caller-time e*))) (set-edge-callee-time! e* (+ (edge-callee-time e) (edge-callee-time e*)))])) (for ([e (node-callees node)]) (define callee* (translate table (edge-callee e))) (match (findf (λ (e2) (eq? (edge-callee e2) callee*)) (node-callees node*)) [#f (define e* (struct-copy edge e [callee callee*] [caller node*])) (set-node-callees! node* (cons e* (node-callees node*)))] [e* (set-edge-total! e* (+ (edge-total e) (edge-total e*))) (set-edge-caller-time! e* (+ (edge-caller-time e) (edge-caller-time e*))) (set-edge-callee-time! e* (+ (edge-callee-time e) (edge-callee-time e*)))]))) (define (node-loc node) (cons (node-id node) (node-src node))) (define (merge-thread-times . ts) (define h (make-hash)) (for* ([t (in-list ts)] [(id time) (in-dict t)]) (hash-update! h id (curry + time) 0)) h) (define (profile->json p) (define nodes (cons (profile-*-node p) (profile-nodes p))) (define loc-hash (for/hash ([node (in-list nodes)] [n (in-naturals)]) (values (node-loc node) n))) (define node-hash (for/hash ([node (in-list nodes)]) (values (node-loc node) node))) (hash 'total_time (exact->inexact (profile-total-time p)) 'cpu_time (exact->inexact (profile-cpu-time p)) 'sample_number (profile-sample-number p) 'thread_times (for/list ([(id time) (in-dict (profile-thread-times p))]) (hash 'id id 'time (exact->inexact time))) 'nodes (for/list ([node nodes]) (hash 'id (and (node-id node) (~a (node-id node))) 'src (and (node-src node) (srcloc->string (node-src node))) 'thread_ids (node-thread-ids node) 'total (exact->inexact (node-total node)) 'self (exact->inexact (node-self node)) 'callers (map (curry edge->json loc-hash) (node-callers node)) 'callees (map (curry edge->json loc-hash) (node-callees node)))))) (define (edge->json loc-hash edge) (hash 'total (exact->inexact (edge-total edge)) 'caller (hash-ref loc-hash (node-loc (edge-caller edge))) 'caller_time (exact->inexact (edge-caller-time edge)) 'callee (hash-ref loc-hash (node-loc (edge-callee edge))) 'callee_time (exact->inexact (edge-callee-time edge)))) (define (string->srcloc s) (match-define (list path-parts ... (app string->number line) (app string->number col)) (string-split s ":" #:trim? #f)) (define path (string->path (string-join path-parts ":"))) (srcloc path line (and line col) #f #f)) (define (json->profile j) (define nodes (for/vector ([n (hash-ref j 'nodes)]) (node (hash-ref n 'id) (and (hash-ref n 'src) (string->srcloc (hash-ref n 'src))) (hash-ref n 'thread_ids) (hash-ref n 'total) (hash-ref n 'self) '() '()))) (for ([n (in-list (hash-ref j 'nodes))] [n* (in-vector nodes)]) (set-node-callees! n* (for/list ([e (hash-ref n 'callees)]) (edge (hash-ref e 'total) (vector-ref nodes (hash-ref e 'caller)) (hash-ref e 'caller_time) (vector-ref nodes (hash-ref e 'callee)) (hash-ref e 'callee_time)))) (set-node-callers! n* (for/list ([e (hash-ref n 'callers)]) (edge (hash-ref e 'total) (vector-ref nodes (hash-ref e 'caller)) (hash-ref e 'caller_time) (vector-ref nodes (hash-ref e 'callee)) (hash-ref e 'callee_time))))) (profile (hash-ref j 'total_time) (hash-ref j 'cpu_time) (hash-ref j 'sample_number) (for/list ([t (hash-ref j 'thread_times)]) (cons (hash-ref t 'id) (hash-ref t 'time))) (rest (vector->list nodes)) (vector-ref nodes 0))) (module+ main (require profile/render-text json) (command-line #:args (file) (render (json->profile (call-with-input-file file read-json)) 'total))) ================================================ FILE: src/utils/timeline.rkt ================================================ #lang racket (require json "../config.rkt" racket/hash) (provide (rename-out [timeline-push! timeline-push!/unsafe] [timeline-start! timeline-start!/unsafe]) (contract-out (timeline-event! (symbol? . -> . void?)) (timeline-push! (symbol? jsexpr? ... . -> . void?)) (timeline-start! (symbol? jsexpr? ... . -> . (-> void?)))) timeline-load! timeline-extract timeline-merge timeline-reattribute-gc *timeline-disabled*) (module+ debug (provide *timeline*)) ;; This is a box so we can get a reference outside the engine, and so ;; access its value even in a timeout. ;; Important: Use 'eq?' based hash tables, process may freeze otherwise (define/reset *timeline* (box '()) (lambda () (set-box! (*timeline*) '()))) (define *timeline-active-key* #f) (define *timeline-active-value* #f) (define *timeline-disabled* (make-parameter true)) (define always-compact '(mixsample outcomes)) (define (timeline-event! type) (when (and *timeline-active-key* (pair? (unbox (*timeline*)))) (hash-update! (car (unbox (*timeline*))) *timeline-active-key* (curry append *timeline-active-value*) '()) (set! *timeline-active-key* #f)) (unless (*timeline-disabled*) (when (pair? (unbox (*timeline*))) (for ([key (in-list always-compact)] #:when (hash-has-key? (car (unbox (*timeline*))) key)) (timeline-compact! key))) (define live-memory (current-memory-use #f)) (define alloc-memory (current-memory-use 'cumulative)) (define b (make-hasheq (list (cons 'type (~a type)) (cons 'time (current-inexact-milliseconds)) (cons 'gc-time (current-gc-milliseconds)) (list 'memory (list live-memory alloc-memory))))) (set-box! (*timeline*) (cons b (unbox (*timeline*)))))) (define (timeline-push! key . values) (unless (*timeline-disabled*) (define val (if (null? (cdr values)) (car values) values)) (cond [(eq? *timeline-active-key* key) (set! *timeline-active-value* (cons val *timeline-active-value*))] [(not *timeline-active-key*) (unless (pair? (unbox (*timeline*))) (error 'timeline "Cannot push '~a to an empty timeline." key)) (set! *timeline-active-key* key) (set! *timeline-active-value* (list val))] [else (when *timeline-active-key* (hash-update! (car (unbox (*timeline*))) *timeline-active-key* (curry append *timeline-active-value*) '())) (set! *timeline-active-key* key) (set! *timeline-active-value* (list val))]))) (define *timeline-1st-timer* #f) (define *timeline-2nd-timer* #f) (define *timeline-timers* (mutable-set)) (define (timeline-start! key . values) (define tstart (current-inexact-milliseconds)) (cond [(not *timeline-1st-timer*) (define (end!) (define tend (current-inexact-milliseconds)) (apply timeline-push! key (- tend tstart) values) (set! *timeline-1st-timer* #f)) (set! *timeline-1st-timer* end!) end!] [(not *timeline-2nd-timer*) (define (end!) (define tend (current-inexact-milliseconds)) (apply timeline-push! key (- tend tstart) values) (set! *timeline-2nd-timer* #f)) (set! *timeline-2nd-timer* end!) end!] [*timeline-1st-timer* ; Slow path, more than one timer at a time (define (end!) (define tend (current-inexact-milliseconds)) (apply timeline-push! key (- tend tstart) values) (set-remove! *timeline-timers* end!)) (set-add! *timeline-timers* end!) end!])) (define (timeline-load! value) (*timeline* value)) (define (diff-memory-records v1 v2) (list (list (- (caar v1) (caar v2)) (- (cadar v1) (cadar v2))))) (define (timeline-extract) (when *timeline-1st-timer* (*timeline-1st-timer*)) (when *timeline-2nd-timer* (*timeline-2nd-timer*)) (when *timeline-active-key* (hash-update! (car (unbox (*timeline*))) *timeline-active-key* (curry append *timeline-active-value*) '()) (set! *timeline-active-key* #f)) (for ([end! (set->list *timeline-timers*)]) (end!)) (define end (hasheq 'time (current-inexact-milliseconds) 'gc-time (current-gc-milliseconds) 'memory (list (list (current-memory-use #f) (current-memory-use 'cumulative))))) (timeline-reattribute-gc (reverse (for/list ([evt (in-list (unbox (*timeline*)))] [next (in-list (cons end (unbox (*timeline*))))]) (define evt* (hash-copy evt)) (hash-update! evt* 'time (λ (v) (- (hash-ref next 'time) v))) (hash-update! evt* 'gc-time (λ (v) (- (hash-ref next 'gc-time) v))) (hash-update! evt* 'memory (λ (v) (diff-memory-records (hash-ref next 'memory) v))) evt*)))) (define timeline-types (make-hasheq)) (define-syntax define-timeline (syntax-rules () [(_ name [field] ...) (hash-set! timeline-types 'name append)] [(_ name (field type) ...) (hash-set! timeline-types 'name (procedure-rename (make-merger type ...) 'name))] [(_ name #:custom fn) (hash-set! timeline-types 'name fn)] [(_ name #:unmergable) (hash-set! timeline-types 'name #f)])) (define ((make-merger . fields) . tables) (define rows (apply append tables)) (define groups (make-hash)) (for ([row rows]) (define-values (values key*) (partition cdr (map cons row fields))) (define key (map car key*)) (if (hash-has-key? groups key) (hash-update! groups key (λ (old) (for/list ([value2 old] [(value1 fn) (in-dict values)]) (fn value2 value1)))) (hash-set! groups key (map car values)))) (for/list ([(k v) (in-hash groups)]) (let loop ([fields fields] [k k] [v v]) (match* (fields k v) [((cons #f f*) (cons k k*) v) (cons k (loop f* k* v))] [((cons _ f*) k (cons v v*)) (cons v (loop f* k v*))] [('() '() '()) '()])))) (define (merge-sampling-tables l1 l2) (let loop ([l1 (sort l1 < #:key first)] [l2 (sort l2 < #:key first)]) (match-define (list n1 t1) (car l1)) (match-define (list n2 t2) (car l2)) (define rec (list n1 (hash-union t1 t2 #:combine +))) (match* ((cdr l1) (cdr l2)) [('() '()) (list rec)] [('() l2*) (cons rec (loop (list (list (+ n1 1) t1)) l2*))] [(l1* '()) (cons rec (loop l1* (list (list (+ n2 1) t2))))] [(l1* l2*) (cons rec (loop l1* l2*))]))) ;; Generic & universal (define-timeline type #:custom (λ (a b) a)) (define-timeline time #:custom +) ;; Handled with separate GC phase (define-timeline gc-time #:custom +) (define-timeline memory [live +] [alloc +]) (define-timeline allocations [phase false] [memory +]) (define-timeline method [method]) (define-timeline mixsample [time +] [function false] [precision false] [memory +]) (define-timeline times [time +] [input false]) (define-timeline series [time +] [var false] [transform false]) (define-timeline compiler [before +] [after +]) (define-timeline outcomes [time +] [prec false] [category false] [count +]) (define-timeline accuracy [accuracy]) (define-timeline oracle [oracle]) (define-timeline baseline [baseline]) (define-timeline count [input +] [output +]) (define-timeline alts #:unmergable) (define-timeline batch #:unmergable) (define-timeline inputs #:unmergable) (define-timeline outputs #:unmergable) (define-timeline sampling #:custom merge-sampling-tables) (define-timeline bogosity #:custom (λ (x y) (list (hash-union (car x) (car y) #:combine +)))) (define-timeline symmetry #:unmergable) (define-timeline bstep #:unmergable) (define-timeline kept #:unmergable) (define-timeline min-error #:unmergable) (define-timeline egraph #:unmergable) (define-timeline stop [reason false] [count +]) (define-timeline branch #:unmergable) (define (timeline-merge . timelines) ;; The timelines in this case are JSON objects, as above (define types (make-hash)) (for* ([tl (in-list timelines)] [event tl]) (define data (hash-ref! types (hash-ref event 'type) (make-hash))) (for ([(k v) (in-dict event)] #:when (hash-ref timeline-types k #f)) (if (hash-has-key? data k) (hash-update! data k (λ (old) ((hash-ref timeline-types k) v old))) (hash-set! data k v)))) (sort (timeline-reattribute-gc (hash-values types)) > #:key (curryr hash-ref 'time))) (define (timeline-compact! key) (unless (*timeline-disabled*) (define fn (hash-ref timeline-types key #f)) (when fn (hash-update! (car (unbox (*timeline*))) key (curryr fn '()) '())))) (define (timeline-reattribute-gc timeline) (define total-gc-time (for/sum ([phase (in-list timeline)]) (hash-ref phase 'gc-time 0))) (define allocations-by-phase (make-hash)) (for ([phase (in-list timeline)]) (define type (hash-ref phase 'type "unknown")) (define alloc (second (first (hash-ref phase 'memory '((0 0)))))) (hash-update! allocations-by-phase type (curry + alloc) 0)) (define allocation-table (for/list ([(type alloc) (in-hash allocations-by-phase)]) (list type alloc))) (define adjusted-phases (for/list ([phase (in-list timeline)]) (define gc-time (hash-ref phase 'gc-time 0)) (define new-time (- (hash-ref phase 'time 0) gc-time)) (define phase* (hash-copy phase)) (hash-set! phase* 'time new-time) (hash-remove! phase* 'gc-time) (hash-remove! phase* 'memory) phase*)) (define gc-phase (make-hasheq (list (cons 'type "gc") (cons 'time total-gc-time) (cons 'allocations allocation-table)))) (if (zero? total-gc-time) adjusted-phases (append adjusted-phases (list gc-phase)))) ================================================ FILE: www/aec.html ================================================ Herbie AEC Instructions
    Herbie

    Herbie (#61)

    AEC Evaluation

    These are instructions for evaluating Herbie, the artifact for PLDI 2015 paper #61. The main downloads for this artifact are the Submitted paper and the VirtualBox Image, along with these instructions.

    Installing Herbie

    There are three ways to try out Herbie. The simplest is to use a VirtualBox image to run Herbie; for users familiar with Docker, Herbie provides a Docker image which may be more convenient; and Herbie can also be built and run from source.

    Virtual machine

    To run Herbie in a virtual machine, download the virtual machine image, start VirtualBox, and start the image in VirtualBox. (VMs other than VirtualBox should also work; however, this has not been tested.)

    The virtual machine will start into a graphical desktop with two icons on the desktop:

    • Results, which holds Herbie's output. It contains a recent run's results.
    • README.html, which are a copy of these instructions.

    In the virtual machine, Herbie can be run with the herbie command. In case you want to install additional software in the VM, the machine is a standard Ubuntu 14.04 Desktop installation, with username aec and password password.

    Docker image

    To run Herbie through Docker, install Docker and download the Herbie image to your computer with:

    docker pull pldi15num61/herbie

    Create a folder for Herbie to place its results into:

    mkdir Results

    You can now run Herbie with the incantation

    docker run -it -v $PWD/Results/:/herbie/graphs pldi15num61/herbie

    For convenience create an alias for this command in your shell. In Bash, you would do this by executing:

    alias herbie=docker run -it -v $PWD/Results/:/herbie/graphs pldi15num61/herbie

    Installing from source

    Herbie is developed on Github in Racket. To run Herbie, you'll need to install Racket. Take care to use the official installer, instead of using your distribution's package manager or a tool like OS X Homebrew. These repositories often have out-of-date Racket version (Herbie requires 6.1) or buggy versions of Racket's bundled mathematics libraries. Note that Herbie's git history contains the names of Herbie's authors, so this method may sacrifice double-blind evaluation.

    Herbie's source can be downloaded with:

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

    Build Herbie by running:

    cd herbie && raco make herbie/reports/make-report.rkt

    Herbie can now be run with:

    racket herbie/reports/make-report.rkt

    For convenience create an alias for this command in your shell. In Bash, you would do this by executing:

    alias herbie=racket herbie/reports/make-report.rkt

    Unlike for the virtual machine or the Docker image, results will appear in graphs/ inside the Herbie source directory.

    Evaluating Herbie

    Now that Herbie is installed and can be run, there are several experiments you can perform to reproduce the results in the paper. These instructions assume the herbie alias has been defined as in the instructions above.

    Reproducing the main evaluation

    To reproduce the results from the main evaluation, run:

    herbie bench/hamming

    This command will take a while to run and demands at least two gigabytes of memory to complete. (Runtime can be anywhere from five minutes to an hour, depending on the number of CPUs available, the available memory, and the speed of the machine. In a virtual machine, this may take longer yet).

    Once complete, open report.html, from the results folder, in a browser. (The page has been mostly tested in Firefox, but should work in all modern browsers.) Note that each invocation of herbie will overwrite this report page. The top of the page should contain a graphic similar to the double precision results in Figure 7 from the paper.

    The results in the figure may not be exactly identical to that in the paper, due to the following reasons:

    • Herbie has improved somewhat since the submitted paper, so may produce somewhat better results on a few tests.
    • The plot drawn on the results page is generated by sampling 8196 points, instead of the 100 000 used in the paper.
    • The random seed used by Herbie is different on every evaluation, which changes the sample points used by Herbie and thus its results.

    We do not expect any of these sources of error to lead to significant difference in the results.

    The rest of the report contains various details of how Herbie achieved its results and several metrics for evaluating them. We did not discuss these metrics in the paper, but invite the artifact evaluator to explore them. For each benchmark, the Target bits column represents the average bits correct for Hamming's answer, when known.

    Reproducing the extended test suite evaluation

    To reproduce Herbie's results on the extended evaluation, execute:

    herbie bench

    This command may take several hours to execute, and is expected to require as much as four gigabytes of memory. The results will again be summarized in report.html. Note that for the numeric results reported in the paper, only some of the test cases were considered. (Herbie's complete benchmarks contain several trivial or duplicate benchmarks, since the same formula sometimes shows up in multiple places; these were ignored in the reported results.)

    More things to do with Herbie

    Herbie supports several additional options, which can be used to explore the effect of other parameters. These options are summarized by herbie --help:

    -r R
    A random seed to use; a fixed seed is stored in the SEED environment variable. Omitting this argument asks Herbie to choose a new seed.
    -n N
    Number of iterations of the main loop to use.
    -s N
    Number of sample points to use during the internal search.
    -f category:flag
    Toggle flags that govern Herbie's search. This option can be repeated. If both sample:double and precision:double are toggled, Herbie will search for improvements in single-precision mode. The other flags turn off various parts of Herbie's search, and are not recommended.

    Writing new tests

    New benchmarks can also be written and passed to Herbie. To do this, create a new file in bench/ named something.rkt. This file should be in a standard format; see bench/basic.rkt for an example. Herbie should now be run so:

    herbie bench/something.rkt

    A report is produced as usual.

    ================================================ FILE: www/demo.js ================================================ CONSTANTS = ["PI", "E"] FUNCTIONS = { "+": [2], "-": [1, 2], "*": [2], "/": [2], "fabs": [1], "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] } SECRETFUNCTIONS = {"^": "pow", "**": "pow", "abs": "fabs"} function tree_errors(tree) /* tree -> list */ { var messages = []; var names = []; bottom_up(tree, function(node, path, parent) { switch(node.type) { case "ConstantNode": if (node.valueType !== "number") messages.push("Constants that are " + node.valueType + "s not supported."); break; case "FunctionNode": node.name = SECRETFUNCTIONS[node.name] || node.name; if (!FUNCTIONS[node.name]) { messages.push("Function " + node.name + " unsupported."); } else if (FUNCTIONS[node.name].indexOf(node.args.length) === -1) { messages.push("Function " + node.name + " expects " + FUNCTIONS[node.name].join(" or ") + " arguments"); } break; case "OperatorNode": node.op = SECRETFUNCTIONS[node.op] || node.op; if (!FUNCTIONS[node.op]) { messages.push("Operator " + node.op + " unsupported."); } else if (FUNCTIONS[node.op].indexOf(node.args.length) === -1) { messages.push("Operator " + node.op + " expects " + FUNCTIONS[node.op].join(" or ") + " arguments"); } break; case "SymbolNode": if (CONSTANTS.indexOf(node.name) === -1) names.push(node.name); break; default: messages.push("Unsupported syntax; found unexpected " + node.type + ".") break; } }); if (names.length == 0) { messages.push("No variables mentioned."); } return messages; } function bottom_up(tree, cb) { if (tree.args) { tree.args = tree.args.map(function(node) {return bottom_up(node, cb)}); tree.res = cb(tree); } else { tree.res = cb(tree); } return tree; } function dump_tree(tree, txt) /* tree string -> string */ { function extract(args) {return args.map(function(n) {return n.res});} var names = []; var body = bottom_up(tree, function(node) { switch(node.type) { case "ConstantNode": return "" + node.value; case "FunctionNode": node.name = SECRETFUNCTIONS[node.name] || node.name; return "(" + node.name + " " + extract(node.args).join(" ") + ")"; case "OperatorNode": node.op = SECRETFUNCTIONS[node.op] || node.op; return "(" + node.op + " " + extract(node.args).join(" ") + ")"; case "SymbolNode": if (CONSTANTS.indexOf(node.name) === -1) names.push(node.name); return node.name; default: throw SyntaxError("Invalid tree!"); } }); var dnames = []; for (var i = 0; i < names.length; i++) { if (dnames.indexOf(names[i]) === -1) dnames.push(names[i]); } var name = txt.replace("\\", "\\\\").replace("\"", "\\\""); return "(FPCore (" + dnames.join(" ") + ") :name \"" + name + "\" " + body.res + ")"; } function onload() /* null -> null */ { var form = document.getElementById("formula"); var input = document.querySelector("#formula input"); input.setAttribute("name", "formula-math"); input.setAttribute("placeholder", "sqrt(x + 1) - sqrt(x)"); input.removeAttribute("disabled"); var hidden = document.createElement("input"); hidden.type = "hidden"; hidden.setAttribute("name", "formula"); form.appendChild(hidden); document.getElementById("mathjs-instructions").style.display = "block"; document.getElementById("lisp-instructions").style.display = "none"; input.addEventListener("keyup", function(evt) { var txt = input.value; var tree, errors = []; try { tree = math.parse(txt); errors = tree_errors(tree); } catch (e) { errors = ["" + e]; } if (txt && errors.length > 0) { document.getElementById("errors").innerHTML = "
  • " + errors.join("
  • ") + "
  • "; } else { document.getElementById("errors").innerHTML = ""; } }); form.addEventListener("submit", function(evt) { var txt = input.value; var tree, errors; try { tree = math.parse(txt); errors = tree_errors(tree); } catch (e) { errors = ["" + e]; } if (errors.length > 0) { document.getElementById("errors").innerHTML = "
  • " + errors.join("
  • ") + "
  • "; evt.preventDefault(); return false; } else { document.getElementById("errors").innerHTML = ""; } var lisp = dump_tree(tree, txt); hidden.setAttribute("value", lisp); var url = document.getElementById("formula").getAttribute("data-progress"); if (url) { input.disabled = "true"; ajax_submit(url, txt, lisp); evt.preventDefault(); return false; } else { return true; } }); } function clean_progress(str) { var lines = str.split("\n"); var outlines = []; for (var i = 0; i < lines.length; i++) { var line = lines[i]; var words = line.split(" "); var word0 = words.shift(); outlines.push(htmlescape((word0.substring(0, 6) === "* * * " ? "* " : "") + words.join(" "))); } return outlines.join("\n"); } function htmlescape(str) { return ("" + str).replace("&", "&").replace("<", "<").replace(">", ">"); } function get_progress(loc) { var req2 = new XMLHttpRequest(); req2.open("GET", loc); req2.onreadystatechange = function() { if (req2.readyState == 4) { if (req2.status == 202) { document.getElementById("progress").innerHTML = clean_progress(req2.responseText); setTimeout(function() {get_progress(loc)}, 100); } else if (req2.status == 201) { var loc2 = req2.getResponseHeader("Location"); window.location.href = loc2; } else { document.getElementById("errors").innerHTML = req2.responseText; } } } req2.send(); } function ajax_submit(url, text, lisp) { document.getElementById("progress").style.display = "block"; var req = new XMLHttpRequest(); req.open("POST", url); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.onreadystatechange = function() { if (req.readyState == 4) { if (req.status == 201) { var jobcount = req.getResponseHeader("X-Job-Count"); var jobelt = document.getElementById("num-jobs") if (jobelt) jobelt.innerHTML = jobcount - 1; var loc = req.getResponseHeader("Location"); get_progress(loc); } else { document.getElementById("errors").innerHTML = req.responseText; } } } var content = "formula=" + encodeURIComponent(lisp) + "&formula-math=" + encodeURIComponent(text); req.send(content); } window.addEventListener("load", onload); ================================================ FILE: www/doc/0.9/docker.html ================================================ 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 uwplse/herbie -it

    This will run Herbie, reading input from the standard input. To read from a file, you will need to mount the file in the Docker container. Do that with:

    $ docker run -it \
        -v dir:/herbie/bench \
        uwplse/herbie bench/file

    In this command, file is a file you want to run Herbie on, located in dir.

    ================================================ FILE: www/doc/0.9/input.html ================================================ 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

    The general format of an input expression is:

    (herbie-test (inputs ...) "title" expression)

    Each input is a variable, like x, or a variable and a distribution, written [x distribution]. The title is any text that describes the expression and the input is the expression to improve the accuracy of.

    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

    (herbie-test (a b) "hypotenuse" (sqrt (+ (sqr a) (sqr b))))

    Supported functions

    The full list of supported functions and is as follows:

    +, -, *, /, abs
    The usual arithmetic functions
    - is both negation and subtraction
    sqr, sqrt
    Squares and square roots
    exp, log
    Natural exponent and natural log
    pow
    Exponentiation; raising a value to a power
    sin, cos, tan, cotan
    The trigonometric functions
    asin, acos, atan, atan2
    The inverse trigonometric functions
    atan2 is the two-argument inverse tangent
    sinh, cosh, tanh
    The hyperbolic trigonometric functions
    expm1, log1p, hypot
    Specialized numeric functions, as in math.h

    Herbie allows the +, -, *, and / functions to be passed more than two arguments, and all of these functions are taken as left-associative.

    Herbie allows conditional expressions using if: (if cond a b) evaluates the conditional cond and returns either a if it is true or b if it is not. Conditionals may use:

    =, <, >, <=, >=
    The usual comparison operators
    and, or, not
    The usual logical operators

    Intermediate variables can be defined using let*:

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

    Each variable is bound to the associated value, in order, with later values allowed to reference prior values. All the defined values are bound in the body. Note that Herbie treats these intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy.

    Herbie also supports the constants PI and E.

    Distributions

    Herbie allows each variable to specify the distribution it is drawn from. These distributions can be:

    default
    Interpret a random bit pattern as a float
    (uniform a b)
    A uniform real value between a and b
    Both bounds must be numeric constants
    int
    Samples a random 32-bit signed integer
    n
    Always bind the variable to a constant

    Each of these distributions can also be modified:

    (< a dist b)
    Only values between a and b from dist
    Both bounds are optional numeric constants.
    ================================================ FILE: www/doc/0.9/installing-herbgrind.html ================================================ Installing HerbGrind

    Installing HerbGrind

    HerbGrind depends on Valgrind, a binary instrumentation and memory analysis framework. To install HerbGrind, you must first install a C compiler and GNU awk and sed.

    HerbGrind is α software. It is currently known to work only on 64-bit GNU Linux platforms. Support for 32-bit architectures, for OS X, for Clang, and for BSD core tools is still in progress. Please file a bug if HerbGrind does not work on your system.

    Installing GNU AWK and Sed

    HerbGrind currently supports 64-bit GNU Linux with GCC; OS X has known bugs, and other configurations are untested. On Linux, install GCC, AWK, and Sed using distro-provided packages; on Debian-derivatives, use the build-essentials pacakge:

    sudo apt-get install build-essentials

    On OS X, the same tools can be installed using Homebrew:

    brew install gawk
    brew install gnu-sed --with-default-names
    brew install gcc

    You will also want the XCode Command-line Tools on OSX, which you can install with:

    xcode-select --install

    Installing HerbGrind

    Once the GNU tools are installed, download the HerbGrind source from GitHub with:

    git clone https://github.com/uwplse/herbgrind

    If you go to the herbgrind directory, you should see a README.md file, a directory named herbgrind, a directory named bench/, and a variety of other directories. You should also see a Makefile. Make the binary with:

    make compile

    This command will take approximately ten minutes to run for the first time, since it will download and build a custom Valgrind fork. You may want to set the following Make variables;

    TARGET_PLAT
    The platform you want to run HerbGrind on; the default is amd64-darwin on OS X and amd64-linux otherwise.
    ARCH_PRIM and ARCH_SEC
    The primary and secondary architecture versions, which you will want to set for some 64+32-bit configurations. The default is amd64 primary and no secondary.

    You can also configure without compiling using make setup.

    Once HerbGrind is installed and working correctly, check out the usage instructions.

    ================================================ FILE: www/doc/0.9/installing-herbie.html ================================================ Installing Herbie

    Installing Herbie

    Herbie depends on Racket, a dynamic language developed by Northeastern University. To install Herbie, one must first install Racket. Once Racket is installed, Herbie should run without problems.

    Installing Racket

    Herbie currently supports Linux and OS X. Windows has known bugs we are working to resolve, due to the lack of a full math.h library. Use the official installer to install Racket, or use distro-provided packages provided they are version 6.4 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.4.
    > (exit)

    Installing Herbie

    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 herbie, a directory named bench/, and a variety of other directories. Do a trial run of Herbie to make sure everything is installed and working correctly:

    racket src/reports/run.rkt bench/hamming/

    This command will take approximately fifteen minutes to run; it runs Herbie on problems from Richard Hamming's Numerical Methods for Scientists and Engineers, Chapter 3. After the command completes, a directory named graphs should have appeared. Open up the report.html file inside with your browser; you should see a listing of the expressions Herbie was run on, most of which should be green. A score of 24/28 or greater suggests that everything is working correctly.

    If any of the test results are crashes (there will be a “crashes” count at the top), installation issues are to blame. Check that your Racket installation is at least version 6.1 and that you installed Racket from the official installation packages. If you're confident the installation is correct please report the error to the Herbie developers, attaching an archive of the complete graphs/ directory.

    If this all works correctly, you can make Herbie start up faster by byte-compiling it:

    raco make src/reports/run.rkt

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

    ================================================ FILE: www/doc/0.9/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The Herbie command takes several 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.

    General Options

    These options can be set on both the report generator and the one-off tool, and influence the coarse properties of Herbie's search.
    -r or --seed
    This sets the random seed, which changes the randomly-selected points that Herbie evaluates candidate expressions on. The format of the seed is that used by the Racket vector->pseudo-random-generator! function; in practice, just use a seed produced by an earlier run. This option can be used to make Herbie's results reproducible or to compare two different runs.
    --fuel
    The number of improvements Herbie attempts to make to the program. The default, 2, 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 3 or 4 to check that there aren't further improvements that Herbie could seek out.
    --num-points
    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.
    --threads, for the report generator only
    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. This option is not supported on OS X or in Racket 6.2, and will warn if used in those cases.

    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 toggled with the -o or --option command-line flags. 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.
    precision:double
    This option, on by default, runs Herbie in double-precision mode. If turned off, Herbie runs in single-precision mode.
    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.
    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, 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.
    reduce:taylor
    This option, on by default, uses a final set of series expansions after all improvements have been made. This sometimes improves accuracy further. If turned off, this final series expansion pass will not be done. 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.
    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.
    rules:arithmetic
    This option, on by default, allows Herbie to use basic arithmetic facts during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:polynomials
    This option, on by default, allows Herbie to use facts about polynomials during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:fractions
    This option, on by default, allows Herbie to use facts about fractions during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    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.
    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.
    rules:numerics
    This option, off by default, allows Herbie to use special numerical functions. If turned off, Herbie will not be able to use these functions. You will want to turn this option on if these functions (currently expm1, log1p, and hypot) are available in your language.
    ================================================ FILE: www/doc/0.9/using-herbgrind.html ================================================ Using HerbGrind

    Using HerbGrind

    HerbGrind analyzes binaries to find inaccurate floating point expressions. The binaries can come from anywhere—C source, Fortran source, even unknown origins. This tutorial runs HerbGrind on the benchmark programs that HerbGrind ships with.

    Compiling the benchmark program

    HerbGrind ships test binaries in its bench/ directory. You can build them with:

    make -C bench all

    Let's analyze the diff-roots-simple.out binary that you just compiled. Run HerbGrind on that binary with:

    valgrind/herbgrind-install/bin/valgrind --tool=herbgrind bench/diff-roots-simple.out

    This should produce output that looks like this:

    ==16725== HerbGrind, a valgrind tool for Herbie
    ==16725== 
    ==16725== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
    ==16725== Command: bench/diff-roots-simple.out
    ==16725== 
    1.578592e-07
    ==16725== 
    Writing report out to bench/diff-roots-simple.out-errors.gh

    The printed value, 1.578592e-07, is printed by the diff-roots-simple.out binary. HerbGrind writes its results to the named file, bench/diff-roots-simple.out-errors.gh. This file contains one record for each operation; the only operation found in diff-roots-simple.c is:

    (- (sqrt (+ 1.000000 10000000000000.000000)) (sqrt 10000000000000.000000))
    subtraction in main at diff-roots-simple.c:12 (address 400A00)
    43.129555 bits average error
    43.129555 bits max error
    Aggregated over 1 instances

    The first line gives the expression inaccurately evaluated, and the second line gives its location. That line in diff-roots-simple.c is actually:

    y = sqrt(x + 1) - sqrt(x);

    Since this line of code is run only once, HerbGrind doesn't know that x is intended to be a variable, and instead inlines its value.

    The next three lines of the output give the error incurred by the inaccurate computation: 43.1 bits of error over 1 instance of computing that expression.

    Turning HerbGrind on and off

    While running on diff-roots-simple.out, HerbGrind found inaccurate computations not only in diff-roots-simple.out but also in several GNU library calls. HerbGrind has a feature to avoid tracking floating point operations in libraries and other code not within your control by adding instrumentation to your source code.

    Simply surround the numerically-interesting parts of your computation in the HERBGRIND_BEGIN() and HERBGRIND_END() macros:

    // initialization code ...
    HERBGRIND_BEGIN();
    // numerical code ...
    HERBGRIND_END();
    // cleanup code ...

    The diff-roots-simple.c example does this on lines 11 and 13. You can then run HerbGrind with the --start-off flag, which tells HerbGrind not to begin analyzing floating point operations until it sees a HERBGRIND_BEGIN() region:

    valgrind/herbgrind-install/bin/valgrind --tool=herbgrind \
          --start-off bench/diff-roots-simple.out

    The report file now contains only the inaccurate expression described before, and no library computations.

    The HERBGRIND_BEGIN()/HERBGRIND_END() regions can be sprinkled anywhere in your source code; it's common to use them to start HerbGrind only after initializing your program and before cleaning up and outputting results. HerbGrind can be turned on and off multiple times.

    ================================================ FILE: www/doc/0.9/using-herbie.html ================================================ Using Herbie

    Using Herbie

    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 run Herbie on the benchmark programs that Herbie ships with.

    The benchmark programs

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

    (herbie-test (x)
      "Cancel like terms"
      (- (+ 1 x) x))
    
    (herbie-test (x)
      "Expanding a square"
      (- (sqr (+ x 1)) 1))
    
    (herbie-test (x y z)
      "Commute and associate"
      (- (+ (+ x y) z) (+ x (+ y z))))

    This code defines three floating point expressions that we want to run Herbie on: the expression (1 + x) - x, the expression (x + 1)² - 1, and finally the expression ((x + y) + z) - (x + (y + z)). You can check out our input format documentation for more information about how to format Herbie inputs.

    Running Herbie

    Let's analyze these three examples using Herbie. Run Herbie:

    racket src/herbie.rkt

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

    $ racket src/herbie.rkt 
    Seed: #(4084085515 1217806925 3915723770 1268025332 545417352 1092579889) 

    You can now paste inputs directly into your terminal for Herbie to improve:

    (herbie-test (x)
      "Cancel like terms"
      (- (+ 1 x) x))
    [ 1586.644ms]	Cancel like terms	(29→ 0)
    (λ (x) 1)

    Alternatively, you can run Herbie on a file with expressions with:

    $ racket src/herbie.rkt bench/tutorial.rkt > out.rkt
    Seed: #(1637424072 4209802118 1686524629 1009825284 4285017075 2209820745)
    [ 1563.552ms]	Cancel like terms	(29→ 0)
    [ 4839.121ms]	Expanding a square	(38→ 0)
    [ 3083.238ms]	Commute and associate	( 0→ 0)

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

    (λ (x) 1)
    (λ (x) (+ (* x 2) (* x x)))
    (λ (x y z) (- (+ (+ x y) z) (+ x (+ y z))))

    Note that the final expression was not simplified to 0 by Herbie, since the difference in accuracy is negligible.

    For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/1.0/docker.html ================================================ 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 uwplse/herbie -it

    This will run Herbie, reading input from the standard input. To read from a file, you will need to mount the file in the Docker container. Do that with:

    $ docker run -it \
        -v dir:/herbie/bench \
        uwplse/herbie bench/file

    In this command, file is a file you want to run Herbie on, located in dir.

    ================================================ FILE: www/doc/1.0/faq.html ================================================ 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 program” error

    This error is most likely due to an error in preparing the input to Herbie. Common errors include misspelling function names and parenthesizing expressions that must not be parenthesized. The stack trace will contain additional information. For example, in the expression (- (exp (x)) 1), the use of x as invalid because x is a variable, so cannot be parenthesized. (- (exp x) 1) would be the correct way of describing that expression.

    “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 give a distribution which specifies the bounds on x by adding the line :herbie-samplers ([x (uniform -1001 -999)]) after the parameter list.

    ================================================ FILE: www/doc/1.0/input.html ================================================ 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 (+ (sqr a) (sqr b))))

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

    Converting from Herbie 0.9

    Herbie 0.9 used a different input format, which is no longer supported in Herbie 1.0. 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

    Supported functions

    The full list of supported functions and is as follows:

    +, -, *, /, abs
    The usual arithmetic functions
    - is both negation and subtraction
    sqr, sqrt
    Squares and square roots
    exp, log
    Natural exponent and natural log
    pow
    Exponentiation; raising a value to a power
    sin, cos, tan, cotan
    The trigonometric functions
    asin, acos, atan, atan2
    The inverse trigonometric functions
    atan2 is the two-argument inverse tangent
    sinh, cosh, tanh
    The hyperbolic trigonometric functions
    expm1, log1p, hypot
    Specialized numeric functions, as in math.h

    FPCore writes conditional expressions using if:

    (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

    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 you can't use one variable in the value of another; nest let constructs if you want to do that. Note that Herbie treats these intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy.

    Herbie also supports the constants PI and E.

    Properties

    Herbie also uses several FPCore properties for additional meta-data:

    :name string
    Herbie prints this name in its output when a name is required.
    :pre condition
    Only points where the condition is true are sampled by Herbie.
    :herbie-samplers samplers
    Change the distribution with which variables are sampled (see below).

    Distributions

    Herbie allows you to specify what distribution is used to randomly sample values for each variable with the :herbie-samplers property. For example:

    :herbie-samplers ([x (uniform 0 1)] [y (uniform -1 1)])

    This property tells Herbie to use a uniform distribution to sample a value between 0 and 1 for x, and between -1 and 1 for y. Not all variables need to be given distributions; if a variable isn't given a distribution, the default distribution will be used.

    default
    Interpret a random bit pattern as a float
    (uniform a b)
    A uniform real value between a and b
    Both bounds must be numeric constants
    ================================================ FILE: www/doc/1.0/installing-herbgrind.html ================================================ Installing Herbgrind

    Installing Herbgrind

    Herbgrind depends on Valgrind, a binary instrumentation and memory analysis framework. To install Herbgrind, you must first install a C compiler and GNU awk and sed.

    Herbgrind is α software. It is currently known to work only on 64-bit GNU Linux platforms. Support for 32-bit architectures, for OS X, for Clang, and for BSD core tools is still in progress. Please file a bug if Herbgrind does not work on your system.

    Installing GNU AWK and Sed

    Herbgrind currently supports 64-bit GNU Linux with GCC; OS X has known bugs, and other configurations are untested. On Linux, install GCC, AWK, and Sed using distro-provided packages; on Debian-derivatives, use the build-essentials pacakge:

    sudo apt-get install build-essentials

    On OS X, the same tools can be installed using Homebrew:

    brew install gawk
    brew install gnu-sed --with-default-names
    brew install gcc

    You will also want the XCode Command-line Tools on OSX, which you can install with:

    xcode-select --install

    Installing Herbgrind

    Once the GNU tools are installed, download the Herbgrind source from GitHub with:

    git clone https://github.com/uwplse/herbgrind

    If you go to the herbgrind directory, you should see a README.md file, a directory named herbgrind, a directory named bench/, and a variety of other directories. You should also see a Makefile. Make the binary with:

    make compile

    This command will take approximately ten minutes to run for the first time, since it will download and build a custom Valgrind fork. You may want to set the following Make variables;

    TARGET_PLAT
    The platform you want to run Herbgrind on; the default is amd64-darwin on OS X and amd64-linux otherwise.
    ARCH_PRIM and ARCH_SEC
    The primary and secondary architecture versions, which you will want to set for some 64+32-bit configurations. The default is amd64 primary and no secondary.

    You can also configure without compiling using make setup.

    Once Herbgrind is installed and working correctly, check out the usage instructions.

    ================================================ FILE: www/doc/1.0/installing-herbie.html ================================================ Installing Herbie

    Installing Herbie

    Herbie depends on Racket, a dynamic language developed by Northeastern University. To install Herbie, one must first install Racket. Once Racket is installed, Herbie should run without problems.

    Installing Racket

    Herbie currently supports Linux and OS X. Windows has known bugs we are working to resolve, due to the lack of a full math.h library. Use the official installer to install Racket, or use distro-provided packages provided they are version 6.4 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.4.
    > (exit)

    Installing Herbie

    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 herbie, a directory named bench/, and a variety of other directories. Do a trial run of Herbie to make sure everything is installed and working correctly:

    racket src/reports/run.rkt bench/hamming/

    This command will take approximately fifteen minutes to run; it runs Herbie on problems from Richard Hamming's Numerical Methods for Scientists and Engineers, Chapter 3. After the command completes, a directory named graphs should have appeared. Open up the report.html file inside with your browser; you should see a listing of the expressions Herbie was run on, most of which should be green. A score of 24/28 or greater suggests that everything is working correctly.

    If any of the test results are crashes (there will be a “crashes” count at the top), installation issues are to blame. Check that your Racket installation is at least version 6.1 and that you installed Racket from the official installation packages. If you're confident the installation is correct please report the error to the Herbie developers, attaching an archive of the complete graphs/ directory.

    If this all works correctly, you can make Herbie start up faster by byte-compiling it:

    raco make src/reports/run.rkt

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

    ================================================ FILE: www/doc/1.0/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The Herbie command takes several 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.

    General Options

    These options can be set on both the report generator and the one-off tool, and influence the coarse properties of Herbie's search.
    --seed
    This sets the random seed, which changes the randomly-selected points that Herbie evaluates candidate expressions on. The format of the seed is that used by the Racket vector->pseudo-random-generator! function; in practice, just use a seed produced by an earlier run. This option can be used to make Herbie's results reproducible or to compare two different runs.
    --num-iters
    The number of improvements Herbie attempts to make to the program. The default, 2, 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 3 or 4 to check that there aren't further improvements that Herbie could seek out.
    --num-points
    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
    The timeout to use per-example, in seconds. A fractional number of seconds can be given.

    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 toggled with the -o or --option command-line flags. 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.
    precision:double
    This option, on by default, runs Herbie in double-precision mode. If turned off, Herbie runs in single-precision mode.
    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, 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.
    reduce:taylor
    This option, on by default, uses a final set of series expansions after all improvements have been made. This sometimes improves accuracy further. If turned off, this final series expansion pass will not be done. 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.
    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.
    rules:arithmetic
    This option, on by default, allows Herbie to use basic arithmetic facts during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:polynomials
    This option, on by default, allows Herbie to use facts about polynomials during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:fractions
    This option, on by default, allows Herbie to use facts about fractions during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    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.
    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.
    rules:numerics
    This option, off by default, allows Herbie to use special numerical functions. If turned off, Herbie will not be able to use these functions. You will want to turn this option on if these functions (currently expm1, log1p, and hypot) are available in your language.
    ================================================ FILE: www/doc/1.0/release-notes.html ================================================ Herbie 1.0 Release Notes

    Herbie 1.0 Release Notes

    After two years of work, the Herbie developers are excited to announce Herbie 1.0! This is the first release of Herbie, marking its transition from research software to a tool for scientists and engineers to improve the accuracy of their floating point software.

    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.

    Usability improvements

    • A switch to the standard FPCore input format. This makes it easy to use Herbie together with other floating point tools, like Herbgrind.
    • A web demo, which makes it easy to try out Herbie and also gives us a sense of what problems people are trying to solve using Herbie.
    • Easier installation. We've removed dependencies on several external libraries, upgraded to the latest versions of Racket, and eliminated a confusing linking step.
    • OS X support. We've tracked down several bugs that prevented Herbie from working on OS X.
    • Support for more functions. Every function available in math.h is supported, and some additional functions from GNU libc are as well.
    • Docker support for Herbie. You can now use the popular container tool to make installation even easier.
    • More informative report pages, visible in the web demo, which makes them more useful both for users and for debugging. In particular, the new graph gives a quick overview of how Herbie did on a collection of inputs.
    • Make it possible to toggle rulesets in Herbie.
    • A new entry point into Herbie, cleaning up a haphazard collection of different scripts.
    • New website!

    Improvement to core algorithm

    • Herbie is now faster! A new measurement infrastructure helped us track down some time sinks.
    • Bounds on variables. You can now specifiy bounds within which to sample variables.
    • Branch on expressions. Herbie can now sometimes infer conditionals that use expressions, not variables, to determine which expression to use.
    • Support expansions around ±∞ in Taylor expansions. The framework behind this should eventually make it possible to support Puiseux series as well.
    • Simplify expressions such as (exp 1) or (cos PI), by supporting symbolic constants in the simplification algorithm.
    • Faster Taylor series routine. Several large tweaks have helped make Taylor series orders of magnitude faster, which has sped up Herbie.

    Code Cleanup

    • Use a proper parser for input files. We used to use a bizarre system of evaling input files and poking around global storage for the results.
    • Many crashes fixed. Herbie is stabler than ever!
    • A new debugging and logging system, which makes Herbie debugging from the REPL significantly easier
    • A new measurement system that lets us see where Herbie spend its time. This has helped speed up Herbie.
    • A new implementation of the Herbie main loop, which is much easier to work with in the debugger.
    • Unit tests and more integration with the Racket toolchain.
    • A fuzzer for Herbie, which has allowed us to track down many bugs.

    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!

    ================================================ FILE: www/doc/1.0/using-herbgrind.html ================================================ Using Herbgrind

    Using Herbgrind

    Herbgrind analyzes binaries to find inaccurate floating point expressions. The binaries can come from anywhere—C source, Fortran source, even unknown origins. This tutorial runs Herbgrind on the benchmark programs that Herbgrind ships with.

    Compiling the benchmark program

    Herbgrind ships test binaries in its bench/ directory. You can build them with:

    make -C bench all

    Let's analyze the diff-roots-simple.out binary that you just compiled. Run Herbgrind on that binary with:

    valgrind/herbgrind-install/bin/valgrind --tool=herbgrind bench/diff-roots-simple.out

    This should produce output that looks like this:

    ==16725== Herbgrind, a valgrind tool for Herbie
    ==16725== 
    ==16725== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
    ==16725== Command: bench/diff-roots-simple.out
    ==16725== 
    1.578592e-07
    ==16725== 
    Writing report out to bench/diff-roots-simple.out-errors.gh

    The printed value, 1.578592e-07, is printed by the diff-roots-simple.out binary. Herbgrind writes its results to the named file, bench/diff-roots-simple.out-errors.gh. This file contains one record for each operation; the only operation found in diff-roots-simple.c is:

    (FPCore ()
      :type binary64
      (- (sqrt (+ 1.000000 10000000000000.000000)) (sqrt 10000000000000.000000)))
    subtraction in main at diff-roots-simple.c:12 (address 400A00)
    43.129555 bits average error
    43.129555 bits max error
    Aggregated over 1 instances

    The first line gives the expression inaccurately evaluated, and the second line gives its location. That line in diff-roots-simple.c is actually:

    y = sqrt(x + 1) - sqrt(x);

    Since this line of code is run only once, Herbgrind doesn't know that x is intended to be a variable, and instead inlines its value.

    The next three lines of the output give the error incurred by the inaccurate computation: 43.1 bits of error over 1 instance of computing that expression.

    Turning Herbgrind on and off

    While running on diff-roots-simple.out, Herbgrind found inaccurate computations not only in diff-roots-simple.out but also in several GNU library calls. Herbgrind has a feature to avoid tracking floating point operations in libraries and other code not within your control by adding instrumentation to your source code.

    Simply surround the numerically-interesting parts of your computation in the HERBGRIND_BEGIN() and HERBGRIND_END() macros:

    // initialization code ...
    HERBGRIND_BEGIN();
    // numerical code ...
    HERBGRIND_END();
    // cleanup code ...

    The diff-roots-simple.c example does this on lines 11 and 13. You can then run Herbgrind with the --start-off flag, which tells Herbgrind not to begin analyzing floating point operations until it sees a HERBGRIND_BEGIN() region:

    valgrind/herbgrind-install/bin/valgrind --tool=herbgrind \
          --start-off bench/diff-roots-simple.out

    The report file now contains only the inaccurate expression described before, and no library computations.

    The HERBGRIND_BEGIN()/HERBGRIND_END() regions can be sprinkled anywhere in your source code; it's common to use them to start Herbgrind only after initializing your program and before cleaning up and outputting results. Herbgrind can be turned on and off multiple times.

    ================================================ FILE: www/doc/1.0/using-herbie.html ================================================ Using Herbie

    Using Herbie

    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 run Herbie on the benchmark programs that Herbie ships with.

    The benchmark programs

    Herbie ships a collection of binaries 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: the expression (1 + x) - x, the expression (x + 1)² - 1, and finally the expression ((x + y) + z) - (x + (y + z)). You can check out our input format documentation for more information about how to format Herbie inputs.

    Running Herbie

    Let's analyze these three examples using Herbie. Run Herbie:

    racket src/herbie.rkt

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

    $ racket src/herbie.rkt 
    Seed: #(4084085515 1217806925 3915723770 1268025332 545417352 1092579889) 

    You can now paste inputs directly into your terminal for Herbie to improve:

    (FPCore (x)
      :name "Cancel like terms"
      (- (+ 1 x) x))
    [ 1586.644ms]	Cancel like terms	(29→ 0)
    (FPCore (x) :name "Cancel like terms" 1)

    Alternatively, you can run Herbie on a file with expressions with:

    $ racket src/herbie.rkt bench/tutorial.fpcore > out.rkt
    Seed: #(1637424072 4209802118 1686524629 1009825284 4285017075 2209820745)
    [ 1563.552ms]	Cancel like terms	(29→ 0)
    [ 4839.121ms]	Expanding a square	(38→ 0)
    [ 3083.238ms]	Commute and associate	( 0→ 0)

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

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

    Note that the final expression was not simplified to 0 by Herbie, since the difference in accuracy is negligible.

    For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/1.1/compare.js ================================================ 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"); }); }); } ================================================ FILE: www/doc/1.1/docker.html ================================================ 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.

    ================================================ FILE: www/doc/1.1/faq.html ================================================ 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.

    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.

    Reported but unreproduced errors

    We've not been able to reproduce these errors, but please report an issue.

    “place-channel-put” error with threads

    Some users report errors with using Herbie threads.

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

    Early versions of the web shell intermittently showed this error, which seems to be due to the Racket web server library.

    ================================================ FILE: www/doc/1.1/input.html ================================================ 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 (+ (sqr a) (sqr 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
    sqr, sqrt
    Squares and square roots
    cube, cbrt
    Cubes 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.

    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

    Note that unlike the arithmetic operators, these 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.

    Properties

    Herbie also uses several FPCore properties for additional meta-data:

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

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

    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
    ================================================ FILE: www/doc/1.1/installing.html ================================================ Installing Herbie

    Installing Herbie

    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

    Herbie currently supports Linux and OS X. (Windows has known bugs we are working to resolve, due to the lack of a full math.h library.) Use the official installer to install Racket, or use distro-provided packages provided they are version 6.4 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.8.
    > (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.8/. 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.fpcore 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.4, 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.

    ================================================ FILE: www/doc/1.1/options.html ================================================ 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.

    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.

    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 format of the seed is that used by the Racket vector->pseudo-random-generator! function; in practice, just use a seed produced by an earlier run. This option can be used to make Herbie's results reproducible or to compare two different runs.
    --num-iters N
    The number of improvements Herbie attempts to make to the program. The default, 2, 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 3 or 4 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.

    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.
    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:taylor
    This option, on by default, uses a final set of series expansions after all improvements have been made. This sometimes improves accuracy further. If turned off, this final series expansion pass will not be done. 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.
    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.
    rules:arithmetic
    This option, on by default, allows Herbie to use basic arithmetic facts during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:polynomials
    This option, on by default, allows Herbie to use facts about polynomials during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    rules:fractions
    This option, on by default, allows Herbie to use facts about fractions during its search. If turned off, Herbie will not be able to use those facts. You will rarely want to turn this option off.
    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 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. 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 numerical functions. If turned off, Herbie will not be able to use these functions. You will want to turn this option on if these functions (currently expm1, log1p, and hypot) are available in your language.
    ================================================ FILE: www/doc/1.1/release-notes.html ================================================ Herbie 1.1 Release Notes

    Herbie 1.1 Release Notes

    After six months of work, the Herbie developers are excited to announce Herbie 1.1! This release focuses on making Herbie easier to use, including a web interface, clear syntax errors, and detailed reports. This release also expands support for trigonometric and hyperbolic functions, and speeds up preconditions.

    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

    • In line with FPCore 1.0, we have removed support for the cotan function, renamed = to ==, and added the != function.
    • In line with the new syntax for calling Herbie from the command line, the old syntax is deprecated and will be removed in the next version.
    • In line with the faster preconditions, we have deprecated the :herbie-samplers property and will remove it in the next release.
    • We have changed the syntax for specifying search options for Herbie. Instead of using -o to toggle options, use -o to disable options and +o to enable them.

    Usability improvements

    • HTML reports. Herbie can now generate reports that show graphs, describe how Herbie achieved its result, and more.
    • 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 simpler to run Herbie on an FPCore file and save the results in an output file. The command-line shell is also more user-friendly.
    • Easier installation using the Racket package manager.
    • Simpler, clearer syntax errors. Instead of stack traces, syntax errors now come with file, line, and column information.
    Improvement on the Hamming benchmarks from Herbie 1.0 to Herbie 1.1. The improved series expansions, expanded trigonometric and hyperbolic knowledge, and miscellaneous bug fixes have made Herbie smarter.

    Improvement to core algorithm

    • Faster preconditions. A precondition like (< 10 x 12) now causes Herbie to only sample points in the relevant range, significantly speeding up Herbie and obviating many "cannot sample enough valid points" errors.
    • Better support for trigonometric and hyperbolic functions. We've taught Herbie about many trigonometric identities, which it can now use to improve expressions.
    • Better series expansion. We've taught Herbie to take series expansions of many more functions.
    • Zero-argument expressions are now supported.

    Code Cleanup

    • We have a new, right-facing logo!
    • We now have a complete syntax checker for inputs, giving the Herbie core confidence that it's dealing with valid expressions.
    • The infrastructure for generating reports has been cleaned up.
    • The web demo has been rearchitected. It no longer needs to output files, no longer uses continuations, and is now much more configurable.
    • The new command-line syntax unifies the mess of invokable scripts in Herbie. We will continue the unification in future releases.
    • More unit tests continue to be written, and including a test that ensures that all our rules are valid.

    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!

    ================================================ FILE: www/doc/1.1/report.html ================================================ Herbie reports

    Herbie reports

    The Herbie report

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

    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.

    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.

    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).

    Fourth, 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.

    Fifth 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.

    ================================================ FILE: www/doc/1.1/results-new.json ================================================ {"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}]} ================================================ FILE: www/doc/1.1/results-old.json ================================================ {"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)"} ================================================ FILE: www/doc/1.1/using-cli.html ================================================ 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
    Seed: #(3123212801 2137904229 2993294009 3035080405 3708006222 26032508)
    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.

    Batch processing FPCore

    Alternatively, you can run Herbie on a file with multiple expressions in it, producing the output expressions to a file:

    $ herbie improve bench/tutorial.fpcore out.fpcore
    Seed: #(3123212801 2137904229 2993294009 3035080405 3708006222 26032508)
      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.

    ================================================ FILE: www/doc/1.1/using-web.html ================================================ 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 command line.

    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: #(327732824 4211992217 3609811086 1098847098 2827724810 3610427321)
      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"
      (- (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.

    ================================================ FILE: www/doc/1.2/docker.html ================================================ 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 --rm \
        -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 --rm \
        -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. The Herbie Docker image binds to port 80 by default; use the -p <hostport>:80 option to Docker to expose Herbie on whatever port you choose.

    $ docker run -it --rm -p 8000:80 uwplse/herbie

    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.

    ================================================ FILE: www/doc/1.2/faq.html ================================================ 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.

    ================================================ FILE: www/doc/1.2/input.html ================================================ 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
    ================================================ FILE: www/doc/1.2/installing.html ================================================ 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.fpcore 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

    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.

    ================================================ FILE: www/doc/1.2/options.html ================================================ 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.

    ================================================ FILE: www/doc/1.2/release-notes.html ================================================ 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 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 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 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 make it easier to add functions in the future.

    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!

    ================================================ FILE: www/doc/1.2/report.html ================================================ 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.

    ================================================ FILE: www/doc/1.2/using-cli.html ================================================ 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.

    ================================================ FILE: www/doc/1.2/using-web.html ================================================ 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.

    ================================================ FILE: www/doc/1.3/docker.html ================================================ 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, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker command with sudo or run them as the administrative user.

    With Docker installed, download the Herbie image:

    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.

    Note that Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web shell

    Running the web shell in Docker requires exposing the ports inside the container. The Herbie Docker image binds to port 80 by default; use the -p <hostport>:80 option to Docker to expose Herbie on whatever port you choose.

    docker run -it --rm -p 8000:80 uwplse/herbie

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    ================================================ FILE: www/doc/1.3/faq.html ================================================ Herbie FAQ

    Frequently Asked Questions

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors.

    Common errors

    Herbie error messages refer here for additional information and debugging tips.

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be 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

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) only yields a valid result when x is between -1001 and -999, a rather narrow range. The solution is to help out Herbie by specifying a precondition: :pre (< -1001 x -999). Herbie will use the precondition to improve its sampling strategy.

    No valid values

    This error indicates that your precondition excludes all possible inputs. For example, the precondition (< 3 x 2) excludes all inputs. Herbie raises this exception when it can prove that no inputs could work. The solution is to fix the precondition to allow some inputs.

    Exceeded MPFR precision limit

    This rare error indicates that Herbie could not compute a "ground truth" for your expression. For some expressions, like (sin (exp x)), calculating a correct output for large input values requires exponentially many bits. Herbie raises this error when more than 10,000 bits are required.

    Common warnings

    Herbie warnings refer here for explanations and common actions to take.

    Could not determine a ground truth

    Herbie will raise this warning when some inputs require more than 10 000 bits to compute an exact ground truth value. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large exponents would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Native operation not supported on your system

    Some systems do not have native implementations for all operations that Herbie uses. (For example, Microsoft's math.h does not provide the y0 function.) Herbie provides a fallback implementation, but you can disable the fallback with --disable precision:fallback.

    Known bugs

    Some bugs cannot be directly fixed and are documented here.

    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.

    ================================================ FILE: www/doc/1.3/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore input format to specify mathematical expressions, which Herbie searches for accurate implementations of.

    General format

    FPCore format 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
    (where - 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. The arithmetic operators associate to the left.

    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)

    The conditional cond may use:

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

    The comparison functions can take any number of arguments and implement chained comparisons.

    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.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrary floating-point numbers. However, in many cases only a range of argument values are possible. In Herbie, you can describe valid arguments with the :pre property (for “precondition”).

    Preconditions comparison and boolean operators, just like conditional statements. Herbie is particularly efficient when when the precondition is an and of ranges for each variable, such as:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    More complex preconditions do work, but may cause the “Cannot sample enough valid points” error if it is too hard to find points that satisfy the precondition.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Complex Numbersβ

    Herbie includes experimental support for complex numbers; however, this support is currently limited to a few basic operations.

    All input parameters to an FPCore are real numbers; complex numbers must be constructed with complex. The functions re, im, and conj are available on complex numbers, along with the arithmetic operators, exp, log, pow, and sqrt. Complex and real operations use the same syntax, but cannot be mixed: (+ (complex 1 2) 1) is not valid. Herbie reports type errors in such situations.

    Complex operations use the Racket implementation, so results may differ (slightly) from complex numbers in some other 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.

    Miscellaneous Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie's out uses custom FPCore properties to provide additional information about the Herbie improvement process:

    :herbie-status status
    status describes whether Herbie worked: it 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-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
    ([pts err] ...)
    The computed average error of the output program, like above.

    Herbie's passes through :name, :pre, and :precision properties to its outputs.

    The benchmark suite uses other properties (such as :herbie-target) for testing, but these are not supported and their use is discouraged.

    ================================================ FILE: www/doc/1.3/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, 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 7.0 or later of Racket (earlier versions are not supported).

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

    racket
    Welcome to Racket v7.3.
    > (exit)

    Installing Herbie from a package

    Once Racket is installed, install Herbie with:

    raco pkg install --auto herbie

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

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

    Installing Herbie from source

    Once Racket is installed, download and build 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.fpcore 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 7.3, and if the error still persists, please submit a bug.

    For faster startup, to create the herbie command, and to enable plugins, run the following command:

    raco pkg install --name herbie src/

    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, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker command with sudo or run them as the 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. Note that Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    ================================================ FILE: www/doc/1.3/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command several subcommands and options that influence both its user interface and the quality of solutions that it finds.

    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 file or directory input, 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 file or directory input, and produces a directory output of HTML 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 now deprecated.
    --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-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 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
    By default, but not when this option is set, a browser is started to point to the Herbie page. This option also shrinks the a banner printed to the command line.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.). Also useful when Herbie is run through Docker.

    Rulesets

    Herbie uses rewrite rules to make changes to formulas and improve their accuracy. These rules can be turned on and off in groups using --disable rules:group and --enable rules:group. In general, enabling rules improves the accuracy of Herbie's output but may allow it to use functions not available on your platform.

    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
    boolsBoolean operator identities
    branchesif statement simplification

    All groups except numerics are enabled by default. We recommend turning numerics on if these functions are available in your language, and disabling complex or special if those functions are poorly implemented in your language.

    Search options

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

    Each option can be turned off with the -o or --disable command-line flag, and turned on with +o or --enable. The recommended options are the defaults; 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 default to double-precision calculations. If turned off, Herbie defaults to single-precision calculations. This option is a legacy option; use the :precision FPCore property to change precisions instead.
    precision:fallback
    This option, on by default, tells Herbie to use fallback functions if a native implementation is not found for an operation (and print a warning). If turned off, operations with no native implementation will be disabled entirely. Turn this option off if you require Herbie to be faithful to your system's implementation of libm.
    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. Turn this option off if simplifying the input 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. 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. 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). Turn this option off if your programming environment makes branches very expensive, such as in some cases of GPU programming.
    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. 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 inferred 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. Turn this option off if behavior near branches is not important to you.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This can improve accuracy on regime branching, slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables. 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.3 shell
    • herbie-1.0 fileherbie-1.3 improve file -
    • herbie-1.0 files ...cat files ... | herbie-1.3 improve - -
      Alternatively, collect the files into a directory and run herbie-1.3 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.

    ================================================ FILE: www/doc/1.3/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie allows plugins to define additional functions, rewrite rules, and even number representations. Plugins are be separately installed. Once installed, Herbie automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    Note that this plugin uses the SoftPosit library, which only supports Linux platforms, and even then is reported to misbehave on some machines.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (from 8 to 128 bits) and also quires (for 8, 16, and 32 bits) are available, but are poorly supported.

    Developing plugins

    The plugin functionality is currently highly experimental; if you would like to develop your own plugins, please write to the mailing list.

    ================================================ FILE: www/doc/1.3/release-notes.html ================================================ Herbie 1.3 Release Notes

    Herbie 1.3 Release Notes

    The Herbie developers are excited to announce Herbie 1.3! This release focuses on speed and transparency: Herbie 1.3 is nearly twice as fast as Herbie 1.2, and includes cleaner, more comprehensive HTML output.

    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.

    Major features of this release

    Speed: Herbie is roughly twice as fast as in previous releases. Making this happen has involved changes large and small: a clever change to how we use simplification, a new sampling algorithm, and also lots of work tracking down especially slow expressions.

    Transparency: Herbie's web output has become cleaner and more comprehensive. Herbie can now show you its output in C and TeX as well as mathematical notation. You can now specify preconditions and precisions when inputting expressions. And Herbie has a new "Metrics" tab to show in-depth internal information, which will help us continue to improve Herbie.

    Beta features in this release

    Windows support has graduated from beta. We intend to support Windows going forward; if you run into any bugs, please let us know.

    Plugins can now define new number systems and then teach Herbie to use them. There's a new plugin to add support for posit arithmetic. Right now the plugin system is still in flux, so if you'd like to use it, please write to us.

    Herbie 1.3 is roughly 2× faster than Herbie 1.2, thanks to efforts throughout the year. This plot shows how long Herbie's CI takes to run, for every passing CI run this release cycle. In April we added more tests, causing the bump in Travis time around then.

    Improvement to core algorithm

    • Careful performance work has made Herbie nearly three times faster than the 1.2 release.
    • Herbie now uses interval arithmetic to compute "ground truth" values. This makes Herbie's accuracy estimate for a program more correct.
    • Support for single-precision mode has been significantly improved.
    • Series expansion of pows with constant exponents is now much faster.
    • Complex numbers are now handled significantly more quickly.
    • Various fixes have eliminated rare but large slowdowns.

    Usability improvements

    • Herbie's web interface now allows you to change preconditions and precisions (click “additional options” below the formula bar).
    • You can now see C code for Herbie's output—use the drop-down above and to the right of the program box.
    • Herbie has a new website! Hopefully it's a little easier to learn about what Herbie is and how to use it.
    • Herbie now shows preconditions in its HTML output.
    • Herbie now produces somewhat simpler output, for example by simplifying exact constant expressions like (+ 2 2).
    • You can now input if statements on the web using conditional-expression syntax.
    • Herbie will now show warnings in its HTML output, including links to more documentation.
    • Herbie now indents and breaks lines when it prints FPCores in the terminal.
    • Herbie now uses KaTeX to render math in the browser, which is significantly faster than the previous MathJax library.
    • Error and timeout pages now show the input program.

    Code Cleanup

    • Reports now link to an extensive collection of quality and performance metrics. This should help improve Herbie's speed and accuracy over time.
    • Documentation has been improved, with tables of content and explanations of preconditions and precisions.
    • The new reproduce tool allows rerunning a report.
    • The timebar on the metrics page now separates regime inference from binary search.
    • Herbie's JavaScript code has been refactored, making it much easier to maintain.
    • Lots of old, unused code has been deleted, including a lot of support code for the obsolete Herbie Visualizer.
    • Glue code has been moved into a single file, clarifying responsibilities for a lot of modules.

    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!

    ================================================ FILE: www/doc/1.3/report.html ================================================ Herbie reports

    Herbie reports

    The Herbie report

    Herbie can generate HTML reports which give its output expression and also how Herbie found it.

    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 list time Herbie took to improve the program and the precision of floating-point operations.

    Summary numbers from a Herbie report.

    Input and output programs

    Second, the input and output programs themselves. These are printed in standard mathematical syntax. In the top-right corner, the drop-down can be used to change to C syntax or raw TeX.

    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).

    Interactive inputs

    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.

    Reproduction

    Sixth, a command you can use to reproduce this Herbie result. If you find a bug, include the code snippet in this section when filing the bug. Please also include the debug log linked at the top of the page.

    Reproduction information for a Herbie run.

    The top of the page has a right-hand menu bar with additional links. “Log” you to a detailed debug log. “Profile” gives a gprof-style profile. and “Metrics” gives detailed internal metrics on Herbie's results.

    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.

    ================================================ FILE: www/doc/1.3/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/1.3/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie automatically rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; hence the jokes that 0.1 + 0.2 ≠ 0.3 for a computer. But it is hard to understand and fix these inaccuracies, creating mysterious and hard-to-fix bugs. Herbie is a tool to help.

    To get started, download and install Herbie. With Herbie installed, you're ready to begin using it.

    Giving Herbie expressions

    Now that Herbie is installed, start it with:

    herbie web

    After a brief wait, this ought to open a web browser to a page with Herbie's results. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Go ahead and type (1 + x) - x into this box and press enter. You should see the entry box gray out, then some additional text appear on the screen describing the various steps Herbie is doing. Eventually (after a few seconds) you'll be redirected to a page with Herbie's results. The most important part of that page is the large gray box in the middle:

    Input and output program from a Herbie report.

    This shows both the input (1 + x) - x that you gave Herbie, and also Herbie's idea of a more accurate way to evaluate that expression: 1. Here, Herbie did a good job, which you can double check using the statistics above that box:

    Statistics and error measures for this Herbie run.

    Here, Herbie reports that the improved the program has 0 bits of error, on average, whereas the original program had 29.4. That's because, when x is really big, x + 1 = x in floating-point arithmetic, so (x + 1) - x = 0.

    There's lots more information on this results web page to help explain both what the accuracy is on different inputs and to describe how Herbie derived its result.

    Programming with Herbie

    Now that you've run Herbie and know how to read its results, let's work through applying Herbie to a realistic program.

    When you're working on a numerical program, it's best to keep Herbie open in a browser tab so you can run it easily. That way, when you're writing a complex floating-point expression, you can run Herbie to make sure you use the most accurate version of that expression that you can. Herbie has options to log all the expressions you enter, so that you can refer to them later.

    However, if you're tracking down a bug that you think is caused by floating-point error, you'll need to identify the problematic floating-point expression before you can use Herbie on it.

    As an example, let's use math.js, an extensive math library for JavaScript, and walk through bug 208, which found an inaccuracy in the implementation of complex square root. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    Before using Herbie you need to know what floating-point expressions to feed it. In most programs, there's a small core that does the mathematical computations, while the rest of the program 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. The bug we're interested in is about complex square root, so let's look at the file arithmetic/sqrt.js, which contains real and complex square roots.

    The code handles argument checks, five different number types, and error handling. None of that is of interest to Herbie; we want to extract just the mathematical computation. So 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))
      );
    }

    Converting problematic code to Herbie input

    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 send each branch to Herbie separately.

    Also, in this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    Finally, each field of the final output uses the variable r, which is defined in the first line of the code snippet. When you're using Herbie, you want to expand or inline intermediate variables like this, because the definition of that variable contains important information that Herbie can use to improve accuracy.

    Putting this all together, let's do the first field of the non-negative x.im case first. It looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Before running Herbie on this expression, click the “Additional options” link. You should see a box where you can enter a precondition; enter xim <= 0. This makes sure that Herbie only considers the points this expression will actually be run on when improving the accuracy of this expression.

    Using Herbie's results

    Herbie will churn for a few seconds and produce an output, perhaps something like this:

    Herbie's version of the complex square root expression.

    Herbie's algorithm is randomized, so you likely won't see the exact same thing. For example, the branch expression xre ≤ 6.68107529348e-308 will probably have some other really small number. And perhaps Herbie will choose slightly different expressions. But the result should be recognizably similar. In this case, Herbie reports that the initial expression had 38.7 bits of error, and that the output has 29.4.

    It's a little harder to describe what Herbie found wrong with the original expression, and why its new version is better—it is due to a floating-point phenomenon called “cancellation”. But you can get some insight from the error plot just below the program block:

    Herbie's error plot for the complex square root expression.

    There's a lot going on here. Along the horizontal axis, you have the various input values (of xim). Note that the graph is log-scale, and includes only negative values (thanks to our precondition). So in the middle is the value -1, to the left you have values with large exponents approaching infinity, and to the right you have values with small exponents approaching 0.

    On the vertical axis, you have Herbie's error measure (bits of error), from 0 to 64. There are two lines drawn: a red one for your input expression and a blue one for Herbie's output. Lower is better. You can see from the plot that as xim gets larger (toward the right, closer to zero), Herbie's improvement becomes more and more important. Below the plot, there is a list of the argument names, with xim highlighted. If you switch it to xre, you will see that the two expressions are the same for positive xre, and that Herbie's output is better for negative xre. You can also see that the difference is quite large, with Herbie's output expression being much more accurate than its input.

    Note again that Herbie is randomized, and you may see somewhat different output than the screenshots and descriptions here. The overall gist should be similar, however.

    Now that you have the more accurate version of this expression, all you need to do is insert it back into the program:

    var r = Math.sqrt(x.re * x.re + x.im * x.im);
    // Herbie version of 0.5 * Math.sqrt(2.0 * (r + x.re))
    var re;
    if (x.re <= 0) {
        re = Math.abs(x.im) * Math.sqrt(0.5) / Math.sqrt(r - x.re);
    } else {
        re = 0.5 * Math.sqrt(2.0 * (r + x.re));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the original code in place in a comment. That's because the original code is a bit more readable, and it also means that as Herbie gets better, we can re-run it to get future improvements in accuracy.

    By the way, for some languages, like C, you can use the drop-down in the top-right corner of the gray program block to see Herbie's output in that language. You'll probably need to clean up the resulting program a bit, though.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/1.3/using-cli.html ================================================ 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.

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

    Input expressions

    Herbie takes file and command-line input in FPCore syntax. You can find example FPCore files in the bench/ directory in the source code. For example, bench/tutorial.fpcore contains:

    (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”

    The input format documentation contains more details.

    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.3 with seed 2098242187
    Find help on https://herbie.uwplse.org/, 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:

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

    The output suggests the expression 1 as a more accurate variant of the original expression. Note that the ... hides lots of additional information from Herbie, including error estimates and runtime information.

    The Herbie shell makes it easy to play with different expressions and try multiple variants, informed by Herbie's advice.

    Batch processing FPCores

    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
    Starting Herbie on 3 problems (seed: 1809676410)...
      1/3	[   2.202s]   29→ 0	Cancel like terms
      2/3	[  14.875s]   39→ 0	Expanding a square
      3/3	[   8.546s]    0→ 0	Commute and associate

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

    ;; seed: 1809676410
    
    (FPCore (x) ... 1.0)
    (FPCore (x) ... (+ (* x x) (* 2.0 x)))
    (FPCore (x y z) ... 0.0)

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

    ================================================ FILE: www/doc/1.3/using-web.html ================================================ 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.

    Herbie can be used from the command-line or 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 Herbie:

    herbie web
    Herbie 1.3 with seed 841489305
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    The Herbie web shell.

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

    Herbie shows improvement logs as it works.

    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: 1201949741)...
      1/3	[  22.014s]   39→ 0	Expanding a square
      2/3	[   8.616s]    0→ 0	Commute and associate
      3/3	[   1.715s]   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/results.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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/1.4/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is a sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    Herbie can also be installed from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker command with sudo or run them as the administrative user.

    With Docker installed, download the Herbie image:

    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.

    Note that Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web shell

    Running the web shell in Docker requires exposing the ports inside the container. The Herbie Docker image binds to port 80 by default; use the -p <hostport>:80 option to Docker to expose Herbie on whatever port you choose.

    docker run -it --rm -p 8000:80 uwplse/herbie

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    ================================================ FILE: www/doc/1.4/faq.html ================================================ Herbie FAQ

    Common Errors and Warnings

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors, warnings, and known issues.

    Common errors

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

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized. (- (exp x) 1) would be the correct way to write that expression. The input format documentation has more details on Herbie's syntax.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. The simplest fix is to increase the --num-analysis flag. Specifying the range of valid points as a precondition can also help.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. The solution is to fix the precondition or input program.

    Exceeded MPFR precision limit

    This rare error indicates that Herbie could not compute a "ground truth" for your expression. For some expressions, like (sin (exp x)), calculating a correct output for large input values requires exponentially many bits. Herbie raises this error when more than 10,000 bits are required.

    Common warnings

    Herbie warnings refer to this section for explanations and common actions to take.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10 000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large exponents would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Native operation not supported on your system

    Microsoft's math.h does not provide implementations of the Bessel functions. Herbie provides fallback implementations which print this warning. You can disable the fallback with the --disable precision:fallback option; Herbie will then not use those functions.

    Could uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a string representation for a given value. This is likely the result of a bug in a third-party plugin.

    Falling back on regraph because egg-herbie package not installed

    Herbie can use either the egg-herbie or regraph package to simplify expressions; egg-herbie is much faster, but uses compiled binaries that cannot be installed on some systems. Herbie will then fall back to regraph, and get similar results but run roughly twice as long. Install egg-herbie with raco.

    Known bugs

    Bugs that cannot be directly fixed are documented in this section.

    Missing reports chart on Chrome

    When using Chrome to view web pages on your local machine, Herbie reports cannot draw the arrow chart due to security restrictions. Run Chrome with --allow-file-access-from-files to fix this error.

    ================================================ FILE: www/doc/1.4/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    General format

    FPCore format looks like this:

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

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context.

    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))))

    The semicolon (;) character introduces a line comment. 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 include:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. The arithmetic operators associate to the left, and - is used for both subtraction and negation.

    Herbie links against your computer's libm to evaluate these functions. So, each function has the same behavior in Herbie as in your code.

    On Windows, the Bessel functions are not available in the system libm, so Herbie will use a fallback implementation and print a warning. Turn off the the precision:fallback option to disable those functions instead.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison functions implement chained comparisons with more than two arguments.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    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.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will only use the :spec expression to evaluate error, not to search for accurate expressions.

    Miscellaneous Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie's output provide additional information in custom properties:

    :herbie-status status
    status describes whether Herbie worked: it 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-error-input
    ([pts err] ...)
    The average error of the input program at pts points. Multiple entries correspond to Herbie's training and test sets.
    :herbie-error-output
    ([pts err] ...)
    The computed average error of the output program, similar to :herbie-error-input.

    Herbie's benchmark suite also uses properties such as :herbie-target for continuous integration, but these are not supported and their use is discouraged.

    ================================================ FILE: www/doc/1.4/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. It can be installed from a package or from source. To start, install Racket, which Herbie is written in. (Herbie is also available as a Docker image.)

    Installing Racket

    Install Racket, version 7.0 or later, either using the official installer or distro-provided packages. Versions 7.5 and later are significantly faster, and we recommend upgrading.

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

    racket
    Welcome to Racket v7.7.
    > (exit)

    Installing Herbie from a package

    Once Racket is installed, install Herbie from a package with:

    raco pkg install --auto herbie

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path (example paths for Racket 7.7):

    • On Windows, AppData\Roaming\Racket\7.7\bin in your user folder.
    • On macOS, Library/Racket/7.7/bin in your user folder.
    • On Linux, .racket/7.7/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie from source

    Install Rust, using rustup or via some other means. Also install Racket, version 7.0 or later, either using the official installer or distro-provided packages.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

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

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other directories. Install Herbie on your system with:

    make install

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path:

    • On Windows, AppData\Roaming\Racket\7.7\bin in your user folder.
    • On macOS, Library/Racket/7.7/bin in your user folder.
    • On Linux, .racket/7.7/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like an easily-scriptable virtual machine. Herbie in Docker is more limited; we do not recommend using Herbie through Docker without prior Docker experience.

    The Docker documentation describes how to install and run the official uwplse/herbie image.

    ================================================ FILE: www/doc/1.4/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    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. The herbie web command runs Herbie on your local machine and opens your browser to its main page.
    herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie will print its more-accurate version.
    herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. These pages can be viewed in any browser (though with a quirk for Chrome).

    We recommend using web and report, which produce reports with lots of information about floating-point accuracy, including graphs it of error versus input values. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie tool --help to available command-line options for a tool. This command also shows 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, like this:

    herbie report --timeout 60 in.fpcore out/

    Arguments cannot go before options.

    --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. Seeds are not preserved across runs.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of times Herbie attempts to improve accuracy. The default, 4, suffices for most programs and helps keep Herbie fast; in practice iterations beyond the first few rarely lead to lower error. Increase this option, say to 6, to check that there aren't further improvements that Herbie could seek out.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 14. Increasing this option will slow Herbie down, but may fix the "Cannot sample enough valid points" error.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 of the hardware threads.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches of previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.). Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate but more confusing programs.

    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
    boolsBoolean operator identities
    branchesif statement simplification
    specialThe gamma, Bessel, and error functions
    numericsNumerical compounds expm1, log1p, fma, and hypot

    All groups except numerics are enabled by default. We recommend turning numerics on if these functions are available in your language.

    Search options

    These options change the types of transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    precision:fallback
    This option, on by default, tells Herbie to use fallback functions if a native implementation is not found for an operation (and print a warning). If turned off, operations with no native implementation will be disabled entirely. To our knowledge, this option only affects the Bessel functions on Windows.
    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.
    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. 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 Herbie's creativity.
    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. 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.
    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). Turn this option off if your programming environment makes branches very expensive, such as on a GPU.
    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. 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 inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.

    Upgrading from Herbie 1.0

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

    • herbie-1.0herbie-1.4 shell
    • herbie-1.0 fileherbie-1.4 improve file -
    • herbie-1.0 files ...cat files ... | herbie-1.4 improve - -
      Alternatively, collect the files into a directory and run herbie-1.4 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.

    ================================================ FILE: www/doc/1.4/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie plugins define new functions, add rewrite rules, and even implement number representations.

    Herbie plugins are installed separately. Herbie then automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    This plugin uses the SoftPosit library, only supported on Linux. Even then is reported to misbehave on some machines. The plugin support arithmetic operations, sqrt, and quires.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (with 8 or 32 bits) and also quires (for 8, 16, and 32 bit posits) are available, but are poorly supported.

    Complex Numbers

    The complex-herbie plugin implements support for complex numbers. Install it with:

    raco pkg install --auto complex-herbie

    Herbie input parameters are always real numbers; complex numbers must be constructed with complex. The functions re, im, and conj are available on complex numbers, along with the arithmetic operators, exp, log, pow, and sqrt. Complex and real operations use the same syntax, but cannot be mixed: (+ (complex 1 2) 1) is not valid. Herbie reports type errors in such situations.

    Complex operations are implemented by Racket, so results may differ (slightly) from complex numbers in some other language, especially for non-finite complex numbers. In the future, we hope to support complex-number arguments and fully support all complex-number operations.

    Developing plugins

    The plugin functionality is currently highly experimental; if you would like to develop your own plugins, please write to the mailing list.

    ================================================ FILE: www/doc/1.4/release-notes.html ================================================ Herbie 1.4 Release Notes

    Herbie 1.4 Release Notes

    The Herbie developers are excited to announce Herbie 1.4! This release continues a focus on speed and predictability.

    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.

    The Herbie team, working over Zoom to bring you Herbie 1.4

    Major features of this release

    Faster simplification: Herbie now uses the Egg library to simplify expressions. Egg is roughly a hundred times faster than Herbie's existing simplifier; Herbie with Egg is roughly twice as fast overall. Egg is packaged for Windows, macOS, and Linux, so it should be transparently usable on all platforms.

    Input search: Herbie now uses interval arithmetic to more efficiently sample valid inputs. The "Cannot sample enough valid points" error message should now be rarer, because Herbie should be much better at finding valid inputs, whereever they are.

    Herbie 1.4 is substantially faster at simplifying expressions than Herbie 1.3 and 1.2. With timeouts off, these two releases have made simplification almost 4000× faster. Figure from our arXiv submission.

    Improvement to core algorithm

    • Simplification now uses the Egg library; Herbie's existing e-graph library has been spun out as regraph, which can be used by other Racket projects.
    • Herbie's input search pass now analyzes the input program and its precondition and avoids sampling invalid inputs as much as possible.
    • The interval arithmetic library has gained support for movability flags, which allow Herbie to detect and skip unsamplable input points.
    • The core program evaluator now operates in batch mode, speeding Herbie when it generates thousands of program candidates.
    • The interval arithmetic library has been spun out as rival, which other Racket projects can now use. Minor bugs in the interval library have been fixed, and support has been extended to more functions.
    • One form of exponential growth in the rewrite algorithm is fixed by a cache, eliminating a common cause of timeouts.
    • Herbie's "final simplify" step no longer does constant folding, eliminating a rare source of accuracy regression.

    Usability improvements

    • Safari has been tested, and major bugs have been fixed.
    • The documentation is now a bit clearer and more approachable.
    • Herbie now blames the correct variable when a precondition is unsatisfiable.
    • The :spec property and let* construct are now supported.
    • Herbie now tries a little hard to produce simpler expressions. This change won't totally fix Herbie's weird output, but should help a bit.
    • Herbie now aggregates profile information across a whole report and presents it in an interactive, JavaScript-enabled form on the "Metrics" page. This should help us keep speeding up Herbie.
    • Some "internal" measures on the details page now show correct data.

    Code Cleanup

    • Several herbie dependencies have been spun out as libraries, including the C and TeX translators (fpbench), the e-graph implementation (regraph), and the interval library (rival). Other projects can now use these components.
    • Herbie's support for complex number arithmetic has been extracted to a beta-quality external library. In the future we hope to provide a standard plugin API not only for complex numbers but also vectors, matrices, and other mathematical objects.
    • Herbie's support for multi- and mixed-precision operations continues to improve, though there is more to do. We are targeting Herbie 1.5 for this to be "done".
    • Many obsolete functions and data structures have been removed. A new random-number algorithm means pre-1.4 seeds will produce different results on Herbie 1.4.
    • The code base was reorganized to split the web code across multiple files, with cleaner separation between responsibilities.
    • Herbie now uses Github Actions, not Travis, as its continuous integration service. This already means that single-precision and posit support is better tested, and we hope to leverage Github Actions more in the future.

    Try it out!

    We're want Herbie to be 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!

    ================================================ FILE: www/doc/1.4/report.html ================================================ Herbie reports

    Herbie reports

    The Herbie report

    When used in the browser, Herbie generates HTML reports full of information about the accuracy of your input and its output expressions.

    Summary numbers

    First, a brief summary of the results. For most uses, the “Average Error” number, which describes how accurate the input and output expressions are, is the most important statistic in this section. The other numbers list time Herbie took to improve the program and the precision assumed for floating-point operations.

    Summary numbers from a Herbie report. The "binary64" precision refers to double-precision IEEE-754 arithmetic.

    Input and output programs

    Second, the input and output programs themselves, in standard mathematical syntax. In the top-right corner, the drop-down can be used to switch to C syntax or some other format.

    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. You can add a precondition to restrict Herbie to certain inputs in those cases.

    These graphs show the error of the input program with a red line, and the error of the output program with a blue line. Both can be toggled. If the expression has multiple variables, the variable picker on the bottom left selects which variable is placed on the horizontal axis. 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).

    Interactive inputs

    Fourth, a interactive form where you can the output of both your and Herbie's programs. on inputs of your choice Enter argument values on the left, and the input and output programs will be evaluated on those arguments and the results printed on the right.

    The interactive section of a Herbie report. "In" is your program, "Out" is Herbie's.

    Derivation

    Fifth, Herbie's derivation of its output program. These can be helpful in understanding how Herbie works. Each substantive step in the derivation also lists the error, in bits, of that step's output. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program.

    Derivations sometime name arithmetic laws used by Herbie, or they might claim derivation steps are done by simplification, series expansion, or other Herbie strategies. The derivation will also call out any time the input is split into cases. When one part of the program is colored blue, that is the part modified in that derivation step.

    A short derivation from a Herbie report. Note the error, in gray, measured after each derivation step.

    Reproduction

    Sixth, a command to reproduce this Herbie result. If you find a Herbie bug, include this code snippet when filing an issue.

    Reproduction information for a Herbie run. You can use this when submitting bug reports.

    The top of the page has a right-hand menu bar with additional links. “Report” returns you to Herbie's main page. “Log” and “Metrics” give you detailed internal information about Herbie.

    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.

    ================================================ FILE: www/doc/1.4/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/1.4/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 for a computer. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using Herbie.

    Giving Herbie expressions

    Start Herbie with:

    herbie web

    After a brief wait, this will open your web browser and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Type (1 + x) - x into this box and press enter. You should see the entry box gray out, then some text appear on the screen describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results. The most important part of that page is the large gray box in the middle:

    Input and output program from a Herbie report.

    It shows the input, (1 + x) - x, and Herbie's more accurate way to evaluate that expression: 1. Herbie did a good job, which you can see from the statistics at the top of the page:

    Statistics and error measures for this Herbie run.

    The initial program had 29.4 bits of error (on average), while Herbie's better version had 0 bits of error. That's because (1 + x) - x should always be exactly equal to 1, but in floating-point arithmetic, when x is really big, 1 + x rounds down to x and the expression returns 0.

    There's lots more information on this results web page, which you can use explain more about the expression's errors and how Herbie derived its result.

    Programming with Herbie

    Now that you've run Herbie and know how to read its results, let's apply Herbie to a realistic program.

    Herbie's input expressions can come from source code, mathematical models, or even a debugging tool like Herbgrind. But most often, they come from your mind, while you're writing new mathematical code.

    When you're writing a new numerical program, it's best to keep Herbie open in a browser tab so you can run it easily. That way, you can run Herbie on any complex floating-point expression and so always use an accurate version of that expression. Herbie has options to log all the expressions you enter, so that you can refer to them later.

    However, let's suppose you're instead tracking down a floating-point bug in existing code. You'll need start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, an extensive math library for JavaScript. The bug deals with an inaccuracy in the implementation of square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small core that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or print results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. The bug we're interested in is about complex square root, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, five different number types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total this code turns into four Herbie inputs: two output fields, for each of two branches.

    Let's focus on first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative. So, before running Herbie on this expression, click the “Additional options” link, and enter xim >= 0 for the precondition. This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression.

    Using Herbie's results

    Herbie will churn for a few seconds and produce an output, perhaps something like this:

    Herbie's version of the complex square root expression.

    Herbie's algorithm is randomized, so you likely won't see the exact same thing. For example, the branch expression xre ≤ -4.780438341784697e-111 will probably have some other really small number. And perhaps Herbie will choose slightly different expressions. But the result should be recognizably similar. In this case, Herbie reports that the initial expression had 38.7 bits of error, and that the output has 29.4.

    It's a little harder to describe what Herbie found wrong with the original expression, and why its new version is better—it is due to a floating-point phenomenon called “cancellation”. But you can get some insight from the error plot just below the program block. Select the xim variable just below the plot, and you will see something like this:

    Herbie's error plot for the complex square root expression.

    There's a lot going on here. Along the horizontal axis, you have the various values of xim. Note that the graph is log-scale, and includes only non-negative values thanks to our precondition. The value 1 is in the middle; to the left are values with small exponents close to zero, and to the right you have values with large exponents approaching infinity.

    The vertical axis measures bits of error, from 0 to 64. Lower is better. There are two lines drawn: a red one for your input and a blue one for Herbie's output. You can see from the plot that as xim gets smaller (toward the left, closer to zero), Herbie's improvement becomes more and more significant. You can also see that for very large values of xim, the original program had maximal error (in fact, it overflows) but the replace program is often better.

    Of course, your exact output will differ a bit from the screenshots and descriptions here, because Herbie is randomized.

    Now that you have the more accurate version of this expression, all you need to do is insert it back into the program:

    var r = Math.sqrt(x.re * x.re + x.im * x.im);
    // Herbie version of 0.5 * Math.sqrt(2.0 * (r + x.re))
    var re;
    if (x.re <= -4.780438341784697e-111) {
        re = Math.abs(x.im) * Math.sqrt(0.5) / Math.sqrt(r - x.re);
    } else if (x.re <= 1.857088496624289e-105) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else if (x.re <= 117.16871373388169) {
        re = 0.5 * Math.sqrt(2.0 * (r + x.re));
    } else if (x.re <= 5.213930590364927e+88) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.re));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the original code in place in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, like C, you can use the drop-down in the top-right corner of the gray program block to translate Herbie's output to that language.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/1.4/using-cli.html ================================================ Using Herbie from the Command Line

    Using Herbie from the Command Line

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    herbie shell
    Herbie 1.4 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore
      (x)
      ...
      1.0)

    Herbie suggests that 1 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell makes it easy to play with different expressions and try multiple variants, informed by Herbie's advice.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie on 3 problems (seed: 1809676410)...
      1/3	[   0.769s]   29→ 0	Cancel like terms
      2/3	[   1.392s]   39→ 0	Expanding a square
      3/3	[   2.522s]    0→ 0	Commute and associate

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

    ;; seed: 1809676410
    
    (FPCore (x) ... 1.0)
    (FPCore (x) ... (* x (+ x 2.0)))
    (FPCore (x y z) ... 0.0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/1.4/using-web.html ================================================ Using Herbie from the Browser

    Using Herbie from the Browser

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie. Run the Herbie web shell with this command:

    herbie web

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

    herbie web
    Herbie 1.4 with seed 841489305
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    Type expressions in standard mathematical syntax (parsed by Math.js) and hit Enter to have Herbie attempt to improve them.

    Herbie shows improvement logs as it works.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    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: 1201949741)...
      1/3	[  22.014s]   39→ 0	Expanding a square
      2/3	[   8.616s]    0→ 0	Commute and associate
      3/3	[   1.715s]   29→ 0	Cancel like terms

    This command asks Herbie to generate a report from the input expressions in input.fpcore and to save the report in the directory output/. It's best if that directory doesn't exist before running this command.

    Once generated, open output/results.html in your favorite browser (but see the FAQ if you're using Chrome). That page summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see their report.

    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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/1.5/diagrams.html ================================================ Diagrams

    Diagrams

    System diagram of Herbie

    High-level system diagram of Herbie. It highlights Herbie's core architecture, external libraries, and user interactions. Basic flow: Herbie passes user input (expression, precondition, etc.) to the mainloop (scheduler) which alternates between generate and test phases multiple times, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-test phase is complete, Herbie extracts either one or many output expressions using an algorithm called regime inference. Regime inference chooses the "best" (usually most accurate) generated candidate expression or combines multple candidates, each "best" on a smaller part of the input range, with a branch condition.

    ================================================ FILE: www/doc/1.5/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is a sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    Herbie can also be installed from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker command with sudo or run them as the administrative user.

    With Docker installed, download the Herbie image:

    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.

    Note that Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web shell

    Running the web shell in Docker requires exposing the ports inside the container. The Herbie Docker image binds to port 80 by default; use the -p <hostport>:80 option to Docker to expose Herbie on whatever port you choose.

    docker run -it --rm -p 8000:80 uwplse/herbie

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    ================================================ FILE: www/doc/1.5/faq.html ================================================ Herbie FAQ

    Common Errors and Warnings

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors, warnings, and known issues.

    Common errors

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

    Herbie hangs forever

    Herbie 1.5 on Racket 8.0 through 8.2 sometimes hangs due to a bugs in both Herbie and Racket. Please reinstall Herbie 1.5; we re-released it with a fix for this bug.

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized. (- (exp x) 1) would be the correct way to write that expression. The input format documentation has more details on Herbie's syntax.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. The simplest fix is to increase the --num-analysis flag. Specifying the range of valid points as a precondition can also help.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. The solution is to fix the precondition or input program.

    Exceeded MPFR precision limit

    This rare error indicates that Herbie could not compute a "ground truth" for your expression. For some expressions, like (sin (exp x)), calculating a correct output for large input values requires exponentially many bits. Herbie raises this error when more than 10,000 bits are required.

    Common warnings

    Herbie warnings refer to this section for explanations and common actions to take.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10 000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large exponents would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Operator op is deprecated

    Herbie raises this warning when an operation is no longer supported. Input expressions containing these operators may not be portable across different platforms. Consider creating a plugin to support them.

    Could uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a string representation for a given value. This is likely the result of a bug in a third-party plugin.

    Falling back on regraph because egg-herbie package not installed

    Herbie can use either the egg-herbie or regraph package to simplify expressions; egg-herbie is much faster, but uses compiled binaries that cannot be installed on some systems. Herbie will then fall back to regraph, and get similar results but run roughly twice as long. Install egg-herbie with raco.

    Unsound rule application detected

    Herbie uses a set of algebraic rewrite rules in order to simplify expressions, but these rules can sometimes lead to a contradiction. Herbie will automatically compensate for this, and in most cases nothing needs to be done. However, Herbie may have failed to simplify the output.

    Known bugs

    Bugs that cannot be directly fixed are documented in this section.

    Missing reports chart on Chrome

    When using Chrome to view web pages on your local machine, Herbie reports cannot draw the arrow chart due to security restrictions. Run Chrome with --allow-file-access-from-files to fix this error.

    ================================================ FILE: www/doc/1.5/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    General format

    FPCore format looks like this:

    (FPCore (inputs ...) properties ... expression)
    (FPCore name (inputs ...) properties ... expression)

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context. As of version 1.5, Herbie supports named functions. If name is specified, the function can be inlined within any subsequent FPCore in the file.

    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))))

    The semicolon (;) character introduces a line comment. 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 include:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. The arithmetic operators associate to the left, and - is used for both subtraction and negation.

    Herbie links against your computer's libm to evaluate these functions. So, each function has the same behavior in Herbie as in your code.

    On Windows, the Bessel functions are not available in the system libm, so Herbie will use a fallback implementation and print a warning. Turn off the the precision:fallback option to disable those functions instead.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison functions implement chained comparisons with more than two arguments.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    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.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Conversions

    Herbie supports conversions between different precisions. All conversions are assumed to be bi-directional. You can specify conversions with the :herbie-conversions property.

    :herbie-conversions
    ([prec1 prec2] ...)
    If an expression is computed with precision prec1, Herbie is allowed to rewrite all (or some) of the expression so it is computed with precision prec2.

    For example, to let Herbie introduce single-precision code when :precision is set to binary64 or vice versa, specify :herbie-conversions ((binary64 binary32))

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will only use the :spec expression to evaluate error, not to search for accurate expressions.

    Miscellaneous Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie's output provide additional information in custom properties:

    :herbie-status status
    status describes whether Herbie worked: it 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-error-input
    ([pts err] ...)
    The average error of the input program at pts points. Multiple entries correspond to Herbie's training and test sets.
    :herbie-error-output
    ([pts err] ...)
    The computed average error of the output program, similar to :herbie-error-input.

    Herbie's benchmark suite also uses properties such as :herbie-target for continuous integration, but these are not supported and their use is discouraged.

    ================================================ FILE: www/doc/1.5/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. It can be installed from a package or from source. To start, install Racket, which Herbie is written in. (Herbie is also available as a Docker image.)

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.0 are supported, but more recent versions are faster.

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

    racket
    Welcome to Racket v8.1 [cs].
    > (exit)

    Installing Herbie from source

    Install Rust, using rustup or via some other means. Versions as old as 1.46.0 are supported. Also install Racket, version 8.0 or later, either using the official installer or distro-provided packages.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

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

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other directories. Install Herbie on your system with:

    make install

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path (example paths for Racket 8.1):

    • On Windows, AppData\Roaming\Racket\8.1\bin in your user folder.
    • On macOS, Library/Racket/8.1/bin in your home folder.
    • On Linux, .racket/8.1/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie from a package

    Once Racket is installed, install Herbie from a package with:

    raco pkg install --auto herbie

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path:

    • On Windows, AppData\Roaming\Racket\8.1\bin in your user folder.
    • On macOS, Library/Racket/8.1/bin in your home folder.
    • On Linux, .racket/8.1/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like an easily-scriptable virtual machine. Herbie in Docker is more limited; we do not recommend using Herbie through Docker without prior Docker experience.

    The Docker documentation describes how to install and run the official uwplse/herbie image.

    ================================================ FILE: www/doc/1.5/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    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. The herbie web command runs Herbie on your local machine and opens your browser to its main page.
    herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie will print its more-accurate version.
    herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. These pages can be viewed in any browser (though with a quirk for Chrome).

    We recommend using web and report, which produce reports with lots of information about floating-point accuracy, including graphs it of error versus input values. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie tool --help to available command-line options for a tool. This command also shows 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, like this:

    herbie report --timeout 60 in.fpcore out/

    Arguments cannot go before options.

    --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. Seeds are not preserved across runs.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of times Herbie attempts to improve accuracy. The default, 4, suffices for most programs and helps keep Herbie fast; in practice iterations beyond the first few rarely lead to lower error. Increase this option, say to 6, to check that there aren't further improvements that Herbie could seek out.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 14. Increasing this option will slow Herbie down, but may fix the "Cannot sample enough valid points" error.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 of the hardware threads.
    --pareto
    Enables multi-objective improvement. Herbie will attempt to simultaneously optimize for both accuracy and expression cost. Rather than generating a single "ideal" output expression, Herbie will generate many output expressions. This mode is still considered experimental. This will take a long time to run. We recommend timeouts measured in hours.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.). Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate but more confusing programs.

    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
    boolsBoolean operator identities
    branchesif statement simplification
    specialThe gamma, Bessel, and error functions
    numericsNumerical compounds expm1, log1p, fma, and hypot

    All groups except numerics are enabled by default. We recommend turning numerics on if these functions are available in your language.

    Search options

    These options change the types of transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    precision:fallback
    This option, on by default, tells Herbie to use fallback functions if a native implementation is not found for an operation (and print a warning). If turned off, operations with no native implementation will be disabled entirely. To our knowledge, this option only affects the Bessel functions on Windows.
    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.
    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 Herbie's creativity.
    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. 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.
    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). Turn this option off if your programming environment makes branches very expensive, such as on a GPU.
    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. 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 inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.

    Upgrading from Herbie 1.0

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

    • herbie-1.0herbie-1.4 shell
    • herbie-1.0 fileherbie-1.4 improve file -
    • herbie-1.0 files ...cat files ... | herbie-1.4 improve - -
      Alternatively, collect the files into a directory and run herbie-1.4 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.

    ================================================ FILE: www/doc/1.5/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie plugins define new functions, add rewrite rules, and even implement number representations.

    Herbie plugins are installed separately. Herbie then automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    This plugin uses the SoftPosit library, only supported on Linux. Even then is reported to misbehave on some machines. The plugin support arithmetic operations, sqrt, and quires.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (with 8 or 32 bits) and also quires (for 8, 16, and 32 bit posits) are available, but are poorly supported.

    Complex Numbers

    The complex-herbie plugin implements support for complex numbers. Install it with:

    raco pkg install --auto complex-herbie

    Herbie input parameters are always real numbers; complex numbers must be constructed with complex. The functions re, im, and conj are available on complex numbers, along with the arithmetic operators, exp, log, pow, and sqrt. Complex and real operations use the same syntax, but cannot be mixed: (+ (complex 1 2) 1) is not valid. Herbie reports type errors in such situations.

    Complex operations are implemented by Racket, so results may differ (slightly) from complex numbers in some other language, especially for non-finite complex numbers. In the future, we hope to support complex-number arguments and fully support all complex-number operations.

    Developing plugins

    The following is a guide to creating a Herbie plugin. Plugins are considered experimental and may change considerably between releases. If you run into issues, please write to the mailing list.

    First Steps
    All plugins are implemented as Racket packages. The easiest way to initialize a new Racket package is to run

    raco pkg new pkg-name
    in a new folder. Make sure the folder name is the same as the package name! This will initialize a Racket package with all the necessary files. Read the official Racket documentation on the raco tool for more information.

    A single entry needs to be added to the package manifest stored in info.rkt, and Add the following line at the bottom of the file (define herbie-plugin 'name) where name is a unique symbol that doesn't conflict with other Herbie plugins. As a suggestion, this should just be the package name.

    Next, edit the main.rkt file by erasing everything except the language specifier on the first line, and add the line (require herbie/plugin). This gives the package access to the Herbie plugin interface. Optionally add the following for debugging purposes (eprintf "Loading pkg-name support...\n").

    Finally, run the following in the folder containing info.rkt and main.rkt:

    raco pkg install
    This should install your package and check for errors. Now everything is set up! Of course, your plugin is empty and doesn't add any useful features. If you added the debugging line in main.rkt, you should see the string when you run Herbie.

    Adding Features
    Now that you have an empty plugin, you can begin adding new functions, rewrite rules, and number representatons. The procedures exported by the Herbie plugin interface can be roughly divided into two categories: unique and parameterized. Whether or not you use the unique or parameterized half of the interface (or maybe both!) depends entirely on the number representation a feature is being implemented for. First, identify if your number representation is unique or parameterized. For example, if you are adding features for double precision (or rather binary64), the representation is unique. If you are adding features for a generic floating point format, say (float ebits nbits), then the representation is parameterized.

    Plugin Interface (Unique)
    The following are the signatures and descriptions of the plugin procedures for unique representations. These procedures should be called from the top-level of main.rkt rather than inside a function.

    (define-type name (exact? inexact?) exact->inexact inexact->exact)
    Adds a new type with the unique identifier name. The arguments exact? and inexact? return true if a value is an exact or high-precision approximate representation. For Herbie's real type, exact? is implemented with real? and inexact? is implemented with bigfloat?. The procedures exact->inexact and inexact->exact convert between exact? and inexact? values.
    (define-representation (name type repr?) bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Adds a new representation with the unique identifier name. The representation will inherit all rewrite rules defined for type. By default, Herbie defines two types: real and bool. Your representation will most likely inherit from real. The width argument should be the bitwidth of the representation, e.g. 64 for binary64. The argument repr? is a procedure that accepts any argument and returns true if the argument is a value in the representation, e.g. an integer representation should use Racket's integer?, while special? takes a value in the representation and returns true if it is not finite, e.g. NaN or infinity.

    The other four arguments are single-argument procedures that implement different conversions. The first two convert between a value in your representation and a Racket bigfloat (you need to import math/bigfloat). The last two convert between a value in your representation and its corresponding ordinal value. Ordinal values for any representation must be within the interval [0, 2width - 1]. Check Racket's definition of ordinals for floats. Note that those ordinal values can be negative.
    (define-operator (name itypes ...) otype
    [bf bf-fn]
    [ival ival-fn]
    [nonffi nonffi-fn]
    ...)
    Adds a new operator. Operators describe pure mathematical functions, i.e. + or sin. The parameters itypes and otype are the input type(s) and output type. For example, + takes two real inputs and produces one real output. The bf-fn argument is the bigfloat implementation of your operator. The ival-fn argument is the Rival implementation of your operator. This is optional but improves the quality of Herbie's output. If you don't want to implement this, set ival-fn to false. The nonffi-fn argument is the exact implementation of your operator (see define-type for a description of exact). To define operators with an unknown number of arguments, e.g. comparators, add the attribute [itype itype]. This will override the input types defined by itypes.
    (define-operator-impl (op name ireprs ...)orepr
    [fl fl-fn]
    ...)
    Implements op with input representation(s) ireprs and output representation orepr. The field name must be unique. For example, Herbie implements +.f64 and +.f32 for double- and single-precision floats. The argument fl-fn is the actual procedure that does the computation. Like define-operator, the input representations can be overridden with [itype irepr]. By default, the attributes bf, ival, and nonffi are inherited from op but can be overridden as previously described.
    (define-constant name [bf bf-thunk] [ival ival-thunk])
    Adds a new constant. Constants describe pure mathematical values. i.e. π or e. The bf-fn argument is a thunk that returns the bigfloat value of the constant. The ival-fn argument is a thunk that returns the Rival interval value of the constant.
    (define-constant-impl (const name)repr
    [fl fl-thunk]
    ...)
    Implements const for the representation repr. The argument fl-thunk is a thunk that returns the approximate value of the constant in the representation repr. By default, the attributes bf and ival are inherited from const but can be overridden (see define-operator or define-operator-impl for overriding attributes).
    (define-ruleset name (groups ...) #:type ([var repr] ...)
    [rule-name match replace]
    ...)
    Defines a set of rewrite rules. The name of the ruleset as well as each rule-name must be a unique symbol. Each ruleset must be marked with a set of groups (read here on ruleset groups). Each rewrite rule takes the form match ⇝ replace where Herbie's rewriter will replace match with replace (not vice-versa). Each match and replace is an expression whose operators are the names of operator implementations rather than pure mathematical operators. Any variable must be listed in the type information with its associated representation. See the softposit-herbie plugin for a more concrete example.
    (define-ruleset* name (groups ...) #:type ([var type] ...)
    [rule-name match replace]
    ...)
    Like define-ruleset, but it defines a ruleset for every representation that inherits from type. Currently, every type must be the same, e.g. all real, for this procedure to function correctly. Unlike define-ruleset, match and replace contain the names of operators rather than operator implementations.

    Plugin Interface (Parameterized)
    Defining operators, constants, and representations for parameterized functions requires a generator procedure for just-in-time loading of features for a particular representation. When Herbie encounters a representation it does not recognize (not explicitly defined using define-representation) it queries a list of generators in case the representation requires just-in-time loading.

    The following procedure handles generators:

    (register-generator! gen)
    Adds a generator procedure to Herbie's set of generators. Generator procedures take the name of a representation and return whether it successfully created the operators, constants, and rules associated with a particular representation. In the case that your plugin does not define the requested representation, the generator procedure(s) need not do anything and should just return false.

    To actually add representations, operators, etc. within a generator procedure, you must use a set of alternate procedures.

    (register-representation! name
    type
    repr?
    bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Like define-representation, but used within generators.
    (register-operator! op name itypes otype attribs)
    Like define-operator, but used within generators. The argument itypes is a list of the input types while the argument attribs are the same attributes for define-operator, e.g. bf. In this case, attribs is an association: (list (cons 'bf bf-fn) ...).
    (register-operator-impl! op name ireprs orepr attribs)
    Like define-operator-impl, but used within generators. See register-operator! for a description of ireprs and attribs.
    (register-constant! name attribs)
    Like define-constant, but used within generators. The argument attribs are the same attributes for define-constant. In this case, attribs is an association: (list (cons 'bf bf-thunk) ...).
    (register-constant-impl! const name type attribs)
    Like define-constant-impl, but used within generators. See register-constant! for a description of attribs.
    (register-ruleset! name groups var-reprs rules)
    Like define-ruleset, but used within generators. In this case, groups is a list of rule groups; var-reprs is an association pairing each variable in the ruleset with its representation, e.g. (list (cons 'x '(float 5 16)) ...); and rules is a list of rules of the following form (list (list rule-name match replace) ...).
    ================================================ FILE: www/doc/1.5/release-notes.html ================================================ Herbie 1.5 Release Notes

    Herbie 1.5 Release Notes

    The Herbie developers are excited to announce Herbie 1.5! This release focuses on multiple precisions, new syntax, and higher accuracy. Our favorite features are below.

    The Herbie team, working over Zoom to bring you Herbie 1.5

    What is Herbie? Herbie automatically improves the accuracy of floating point expressions. This avoids the bugs, errors, and surprises that so often occur with floating point arithmetic.

    Join us! Join the Herbie developers on the FPBench Slack, at the monthly FPBench community meetings, and at FPTalks 2021, where Brett will be talking about Herbie 1.5.

    A screenshot of Herbie with Pareto mode enabled.

    New Pareto mode

    Traditionally, Herbie has been all about giving you the most accurate way possible to evaluate some mathematical expression. But many of our users need to balance accuracy with speed.

    So Herbie 1.5 has an experimental new pareto mode. When the pareto mode is turned on, Herbie produces multiple outputs that trade off speed for accuracy. Run Herbie with --pareto to try it out, and if you're interested in more details of how it works, check out our paper at ARITH'21.

    \( \begin{array}{l} [a, b] = \mathsf{sort}([a, b]) \\ b \cdot \sqrt{\frac{a}{b}\cdot\frac{a}{b} + 1} \end{array} \)
    Herbie using sorting to improve accuracy.

    Symmetric expressions

    We've been working hard improving Herbie's results on its traditional task: finding the most accurate way to evaluate a mathematical expression. In Herbie 1.5, several improvements landed, but the most exciting is the symmetric expressions mode.

    Simply put, it's pretty common for Herbie to be asked to improve the accuracy of an expression where reordering the variables leaves the expression unchanged—something like log(exp(x) + exp(y)). Often the best way to evaluate that expression requires determining which of x and y is larger.

    With symmetric expressions, Herbie can detect when variable order doesn't matter and sort the variables up front. That often lets Herbie's other components find a clever way to improve accuracy.

    (FPCore dist2 (x y)
      (+ (* x x) (* y y)))
    
    (FPCore (x y)
      (/ (sqrt (+ (dist2 x y) x)) (sqrt 2)))
        
    Using a function definition in FPCore. The first FPCore block defines the dist2 function, and the second uses that definition. The result is shorter and more readable.

    Function definitions

    While most people use Herbie via the web interface, Herbie also supports the standard FPCore format. FPCore offers more control over Herbie, and it's also a good output format for other tools. In this release, we extended Herbie to support function definitions.

    In short, an FPCore can now include a function name between the FPCore keyword and the list of variables. That core can then be referenced by other cores in the same file. Herbie will automatically expand those calls before improving the expression.

    Other improvements

    • Herbie now supports more pluggable number representations, including arbitrary-precision floating-point numbers and also fixed-point numbers. We're still working on better-exposing this to users.
    • Herbie will now introduce some temporary variables to make its output more readable.
    • The series expansion engine was substantially improved, which both reduces timeouts and improves results substantially.
    • A new "retirement community" data structure allows Herbie to consider more candidate programs, and thus produces better, simpler results.
    • You can now give Herbie FPCore input in the web interface.
    • The Herbie timeline data structure (which powers "Metrics" pages) has been dramatically improved, making it easier to add more metrics in the future.

    Deprecations and removals

    • Support for the Racket 7-series is deprecated and will be removed in the next release. Please upgrade.
    • The egg-herbie support package has been merged into Herbie proper. This means that if you want to install Herbie from source, you will now need a Rust compiler.
    • Support for complex numbers has been deprecated and will be removed in the next release. Please let us know if you are using it. We hope to reimplement this eventually, but with a better architecture.
    • The precision:double flag has been deprecated, and no longer does anything. Use the :precision flag instead.
    • The setup:early-exit flag has been deprecated, and no longer does anything. Herbie is fast enough now that it's not that important any more!

    Try it out!

    We want Herbie to be 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!

    ================================================ FILE: www/doc/1.5/report.html ================================================ Herbie reports

    Herbie reports

    The Herbie report

    When used in the browser, Herbie generates HTML reports full of information about the accuracy of your input and its output expressions.

    Summary numbers

    First, a brief summary of the results. For most uses, the “Average Error” number, which describes how accurate the input and output expressions are, is the most important statistic in this section. The other numbers list time Herbie took to improve the program and the precision assumed for floating-point operations.

    Summary numbers from a Herbie report. The "binary64" precision refers to double-precision IEEE-754 arithmetic.

    Input and output programs

    Second, the input and output programs themselves, in standard mathematical syntax. In the top-right corner, the drop-down can be used to switch to C syntax or some other format.

    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. You can add a precondition to restrict Herbie to certain inputs in those cases.

    These graphs show the error of the input program with a red line, and the error of the output program with a blue line. Both can be toggled. If the expression has multiple variables, the variable picker on the bottom left selects which variable is placed on the horizontal axis. 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).

    Interactive inputs

    Fourth, a interactive form where you can the output of both your and Herbie's programs. on inputs of your choice Enter argument values on the left, and the input and output programs will be evaluated on those arguments and the results printed on the right.

    The interactive section of a Herbie report. "In" is your program, "Out" is Herbie's.

    Derivation

    Fifth, Herbie's derivation of its output program. These can be helpful in understanding how Herbie works. Each substantive step in the derivation also lists the error, in bits, of that step's output. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program.

    Derivations sometime name arithmetic laws used by Herbie, or they might claim derivation steps are done by simplification, series expansion, or other Herbie strategies. The derivation will also call out any time the input is split into cases. When one part of the program is colored blue, that is the part modified in that derivation step.

    A short derivation from a Herbie report. Note the error, in gray, measured after each derivation step.

    Reproduction

    Sixth, a command to reproduce this Herbie result. If you find a Herbie bug, include this code snippet when filing an issue.

    Reproduction information for a Herbie run. You can use this when submitting bug reports.

    The top of the page has a right-hand menu bar with additional links. “Report” returns you to Herbie's main page. “Log” and “Metrics” give you detailed internal information about Herbie.

    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.

    ================================================ FILE: www/doc/1.5/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/1.5/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 for a computer. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using Herbie.

    Giving Herbie expressions

    Start Herbie with:

    herbie web

    After a brief wait, this will open your web browser and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Type (1 + x) - x into this box and press enter. You should see the entry box gray out, then some text appear on the screen describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results. The most important part of that page is the large gray box in the middle:

    Input and output program from a Herbie report.

    It shows the input, (1 + x) - x, and Herbie's more accurate way to evaluate that expression: 1. Herbie did a good job, which you can see from the statistics at the top of the page:

    Statistics and error measures for this Herbie run.

    The initial program had 29.4 bits of error (on average), while Herbie's better version had 0 bits of error. That's because (1 + x) - x should always be exactly equal to 1, but in floating-point arithmetic, when x is really big, 1 + x rounds down to x and the expression returns 0.

    There's lots more information on this results web page, which you can use explain more about the expression's errors and how Herbie derived its result.

    Programming with Herbie

    Now that you've run Herbie and know how to read its results, let's apply Herbie to a realistic program.

    Herbie's input expressions can come from source code, mathematical models, or even a debugging tool like Herbgrind. But most often, they come from your mind, while you're writing new mathematical code.

    When you're writing a new numerical program, it's best to keep Herbie open in a browser tab so you can run it easily. That way, you can run Herbie on any complex floating-point expression you're coding up and so always use an accurate version of that expression. Herbie has options to log all the expressions you enter, so that you can refer to them later.

    However, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small core that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or print results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. The bug we're interested in is about complex square root, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, five different number types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total this code turns into four Herbie inputs: two output fields, for each of two branches.

    Let's focus on first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative. So, before running Herbie on this expression, click the “Additional options” link, and enter xim >= 0 for the precondition. This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression.

    Using Herbie's results

    Herbie will churn for a few seconds and produce an output, perhaps something like this:

    Herbie's version of the complex square root expression.

    Herbie's algorithm is randomized, so you likely won't see the exact same thing. For example, the branch expression xre ≤ -4.780438341784697e-111 will probably have some other really small number. And perhaps Herbie will choose slightly different expressions. But the result should be recognizably similar. In this case, Herbie reports that the initial expression had 38.7 bits of error, and that the output has 29.4.

    It's a little harder to describe what Herbie found wrong with the original expression, and why its new version is better—it is due to a floating-point phenomenon called “cancellation”. But you can get some insight from the error plot just below the program block. Select the xim variable just below the plot, and you will see something like this:

    Herbie's error plot for the complex square root expression.

    There's a lot going on here. Along the horizontal axis, you have the various values of xim. Note that the graph is log-scale, and includes only non-negative values thanks to our precondition. The value 1 is in the middle; to the left are values with small exponents close to zero, and to the right you have values with large exponents approaching infinity.

    The vertical axis measures bits of error, from 0 to 64. Lower is better. There are two lines drawn: a red one for the original expression and a blue one for Herbie's version. You can see from the plot that as xim gets smaller (toward the left, closer to zero), Herbie's improvement becomes more and more significant. You can also see that for very large values of xim, the original program had maximal error (in fact, it overflows) but the Herbie's version is better, though not great.

    Of course, your exact output will differ a bit from the screenshots and descriptions here, because Herbie is randomized.

    Now that you have the more accurate version of this expression, all you need to do is insert it back into the program:

    var r = Math.sqrt(x.re * x.re + x.im * x.im);
    // Herbie version of 0.5 * Math.sqrt(2.0 * (r + x.re))
    var re;
    if (x.re <= -4.780438341784697e-111) {
        re = Math.abs(x.im) * Math.sqrt(0.5) / Math.sqrt(r - x.re);
    } else if (x.re <= 1.857088496624289e-105) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else if (x.re <= 117.16871373388169) {
        re = 0.5 * Math.sqrt(2.0 * (r + x.re));
    } else if (x.re <= 5.213930590364927e+88) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.re));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the original code in place in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, like C, you can use the drop-down in the top-right corner of the gray program block to translate Herbie's output to that language.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/1.5/using-cli.html ================================================ Using Herbie from the Command Line

    Using Herbie from the Command Line

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    herbie shell
    Herbie 1.4 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore
      (x)
      ...
      1.0)

    Herbie suggests that 1 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell makes it easy to play with different expressions and try multiple variants, informed by Herbie's advice.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie on 3 problems (seed: 1809676410)...
      1/3	[   0.769s]   29→ 0	Cancel like terms
      2/3	[   1.392s]   39→ 0	Expanding a square
      3/3	[   2.522s]    0→ 0	Commute and associate

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

    ;; seed: 1809676410
    
    (FPCore (x) ... 1.0)
    (FPCore (x) ... (* x (+ x 2.0)))
    (FPCore (x y z) ... 0.0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/1.5/using-web.html ================================================ Using Herbie from the Browser

    Using Herbie from the Browser

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie. Run the Herbie web shell with this command:

    herbie web

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

    herbie web
    Herbie 1.4 with seed 841489305
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    Type expressions in standard mathematical syntax (parsed by Math.js) and hit Enter to have Herbie attempt to improve them.

    Herbie shows improvement logs as it works.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    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: 1201949741)...
      1/3	[  22.014s]   39→ 0	Expanding a square
      2/3	[   8.616s]    0→ 0	Commute and associate
      3/3	[   1.715s]   29→ 0	Cancel like terms

    This command asks Herbie to generate a report from the input expressions in input.fpcore and to save the report in the directory output/. It's best if that directory doesn't exist before running this command.

    Once generated, open output/results.html in your favorite browser (but see the FAQ if you're using Chrome). That page summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see their report.

    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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/1.6/diagrams.html ================================================ Diagrams

    Diagrams

    System diagram of Herbie

    High-level system diagram of Herbie. It highlights Herbie's core architecture, external libraries, and user interactions. Basic flow: Herbie passes user input (expression, precondition, etc.) to the mainloop (scheduler) which alternates between generate and test phases multiple times, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-test phase is complete, Herbie extracts either one or many output expressions using an algorithm called regime inference. Regime inference chooses the "best" (usually most accurate) generated candidate expression or combines multple candidates, each "best" on a smaller part of the input range, with a branch condition.

    ================================================ FILE: www/doc/1.6/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    Herbie can also be installed from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker commands with sudo or run them as the administrative user.

    With Docker installed, you can run the Herbie shell with:

    docker run -it uwplse/herbie shell

    This will read input from the standard input.

    Note that Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web interface

    You can run the Herbie web server locally with

    docker run -it --rm -p 8000:80 uwplse/herbie
    and access the server at http://localhost:8000.

    (Herbie's Docker image binds to port 80 by default; this command uses the -p <hostport>:80 option to expose Herbie on port 8000.)

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    For developers: using Docker

    Updating the Docker image + Dockerfile

    For building and testing, first clone the repo and confirm that Herbie builds correctly with make install.

    Next, examine the Dockerfile and Makefile together. The Dockerfile syntax is described here. The Dockerfile should follow a process exactly like the Makefile, except a clean initial environment is assumed. The build may be split into 2 or more stages to limit the size of the resulting image. Each stage consists of a FROM command and a series of further commands to build up the desired environment, and later stages can refer to earlier stages by name--for example, COPY --from=earlier-stage ... can copy files compiled in earlier images. You may need to do things like bumping the version of Rust used for binary compilation or the version of Racket used in production, or adjusting paths to match the newest version of the repo.

    Once you are ready to build:

    docker build -t herbie-testbuild .
    from the repo's main directory will build a new test image with the tag herbie-testbuild. You can run this image with
    docker run -p 8000:80 -it herbie-testbuild
    and see the web demo in the host machine's browser at http://localhost:8000.

    To open a shell in a running container for testing, first get the container ID with

    docker ps
    and then open a shell in the container as root with
    docker exec -it <CONTAINER ID> sh
    The code and egg-herbie binaries should be under /src.

    Deploying the image

    First, make sure you have access to the docker repo you would like to push the image to (e.g. uwplse/herbie).

    Then, give the image a name and a tag like this:

    docker tag herbie-testbuild uwplse/herbie:tagname
    Once this is done, the tagged image can be pushed to the repo with
    docker push uwplse/herbie:tagname

    If you are pushing a new version, make sure to also update the latest tag:

    docker push uwplse/herbie:latest

    ================================================ FILE: www/doc/1.6/faq.html ================================================ Herbie FAQ

    Common Errors and Warnings

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors, warnings, and known issues.

    Common errors

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

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized. (- (exp x) 1) would be the correct way to write that expression. The input format documentation has more details on Herbie's syntax.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. The simplest fix is to increase the --num-analysis flag. Specifying the range of valid points as a precondition can also help.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. The solution is to fix the precondition or input program.

    Common warnings

    Herbie warnings refer to this section for explanations and common actions to take.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10,000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large exponents would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Using unsound ground truth evaluation

    Herbie's ground truth evaluation does not directly support the Gamma and Bessel functions. Thus, any input containing these operators triggers this warning and causes Herbie to fall back to a slower and unsound ground truth evaluation strategy. There is currently no workaround.

    Operator op is deprecated

    Herbie raises this warning when an operation is no longer supported. Input expressions containing these operators may not be portable across different platforms. Consider creating a plugin to support them.

    Could not uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a string representation for a given value. This is likely the result of a bug in a third-party plugin.

    Unsound rule application detected

    Herbie uses a set of algebraic rewrite rules in order to simplify expressions, but these rules can sometimes lead to a contradiction. Herbie will automatically compensate for this, and in most cases nothing needs to be done. However, Herbie may have failed to simplify the output.

    Unused variable var

    The input FPCore contains a variable that is not used in the body expression.

    Strange variable var

    The input expression contains a variable that is similar in name to named constants, e.g. e instead of E.

    Known bugs

    Bugs that cannot be directly fixed are documented in this section.

    Missing reports chart on Chrome

    When using Chrome to view web pages on your local machine, Herbie reports cannot draw the arrow chart due to security restrictions. Run Chrome with --allow-file-access-from-files to fix this error.

    ================================================ FILE: www/doc/1.6/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    General format

    FPCore format looks like this:

    (FPCore (inputs ...) properties ... expression)
    (FPCore name (inputs ...) properties ... expression)

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context. As of version 1.5, Herbie supports named functions. If name is specified, the function can be inlined within any subsequent FPCore in the file.

    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))))

    The semicolon (;) character introduces a line comment. 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 include:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. The arithmetic operators associate to the left, and - is used for both subtraction and negation.

    Herbie links against your computer's libm to evaluate these functions. So, each function has the same behavior in Herbie as in your code.

    On Windows, the Bessel functions are not available in the system libm, so Herbie will use a fallback implementation and print a warning. Turn off the the precision:fallback option to disable those functions instead.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison functions implement chained comparisons with more than two arguments.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    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.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point
    racket
    Like binary64, but using Racket math functions rather than your computer's libm.

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Conversions

    Herbie supports conversions between different precisions. All conversions are assumed to be bi-directional. You can specify conversions with the :herbie-conversions property.

    :herbie-conversions
    ([prec1 prec2] ...)
    If an expression is computed with precision prec1, Herbie is allowed to rewrite all (or some) of the expression so it is computed with precision prec2 and vice versa.

    For example, to let Herbie introduce single-precision code when :precision is set to binary64 or vice versa, specify :herbie-conversions ((binary64 binary32))

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will only use the :spec expression to evaluate error, not to search for accurate expressions.

    Miscellaneous Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie's output provide additional information in custom properties:

    :herbie-status status
    status describes whether Herbie worked: it 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-error-input
    ([pts err] ...)
    The average error of the input program at pts points. Multiple entries correspond to Herbie's training and test sets.
    :herbie-error-output
    ([pts err] ...)
    The computed average error of the output program, similar to :herbie-error-input.

    Herbie's benchmark suite also uses properties such as :herbie-target for continuous integration, but these are not supported and their use is discouraged.

    ================================================ FILE: www/doc/1.6/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. It can be installed from a package or from source. To start, install Racket, which Herbie is written in. Install Rust when building from source. (Herbie is also available as a Docker image.)

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.0 are supported, but more recent versions are faster.

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

    racket
    Welcome to Racket v8.5 [cs].
    > (exit)

    Installing Herbie from source

    Install Rust, using rustup or via some other means. Versions as old as 1.60.0 are supported. This installation method supports Windows, macOS, and Linux for various architectures.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

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

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other directories. Install Herbie on your system with:

    make install

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path (example paths for Racket 8.5):

    • On Windows, AppData\Roaming\Racket\8.5\bin in your user folder.
    • On macOS, Library/Racket/8.5/bin in your home folder.
    • On Linux, .local/share/racket/8.5/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie from a package

    This installation method supports Windows, macOS, and Linux for x86-64. This method of installation will fail for Apple M1 systems and other ARM architectures. Once Racket is installed, install Herbie from a package with:

    raco pkg install --auto herbie

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path (example paths for Racket 8.1):

    • On Windows, AppData\Roaming\Racket\8.5\bin in your user folder.
    • On macOS, Library/Racket/8.5/bin in your home folder.
    • On Linux, .local/share/racket/8.5/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like an easily-scriptable virtual machine. Herbie in Docker is more limited; we do not recommend using Herbie through Docker without prior Docker experience.

    The Docker documentation describes how to install and run the official uwplse/herbie image.

    ================================================ FILE: www/doc/1.6/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    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. The herbie web command runs Herbie on your local machine and opens your browser to its main page.
    herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie will print its more-accurate version.
    herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. These pages can be viewed in any browser (though with a quirk for Chrome).

    We recommend using web and report, which produce reports with lots of information about floating-point accuracy, including graphs it of error versus input values. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie tool --help to available command-line options for a tool. This command also shows 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, like this:

    herbie report --timeout 60 in.fpcore out/

    Arguments cannot go before options.

    --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. Seeds are not preserved across runs.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of times Herbie attempts to improve accuracy. The default, 4, suffices for most programs and helps keep Herbie fast; in practice iterations beyond the first few rarely lead to lower error. Increase this option, say to 6, to check that there aren't further improvements that Herbie could seek out.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 14. Increasing this option will slow Herbie down, but may fix the "Cannot sample enough valid points" error.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 of the hardware threads.
    --pareto
    Enables multi-objective improvement. Herbie will attempt to simultaneously optimize for both accuracy and expression cost. Rather than generating a single "ideal" output expression, Herbie will generate many output expressions. This mode is still considered experimental. With this option, Herbie will take a long time to run. We recommend timeouts measured in hours.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.). Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate but more confusing programs.

    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
    boolsBoolean operator identities
    branchesif statement simplification
    specialThe gamma, Bessel, and error functions
    numericsNumerical compounds expm1, log1p, fma, and hypot

    Search options

    These options change the types of transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    precision:fallback
    This option, on by default, tells Herbie to use fallback functions if a native implementation is not found for an operation (and print a warning). If turned off, operations with no native implementation will be disabled entirely. To our knowledge, this option only affects the Bessel functions on Windows.
    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.
    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 Herbie's creativity.
    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. 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.
    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). Turn this option off if your programming environment makes branches very expensive, such as on a GPU.
    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. 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 inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.

    Upgrading from Herbie 1.0

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

    • herbie-1.0herbie-1.4 shell
    • herbie-1.0 fileherbie-1.4 improve file -
    • herbie-1.0 files ...cat files ... | herbie-1.4 improve - -
      Alternatively, collect the files into a directory and run herbie-1.4 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.

    ================================================ FILE: www/doc/1.6/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie plugins define new functions, add rewrite rules, and even implement number representations.

    Herbie plugins are installed separately. Herbie then automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    This plugin uses the SoftPosit library, only supported on Linux. Even then is reported to misbehave on some machines. The plugin support arithmetic operations, sqrt, and quires.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (with 8 or 32 bits) and also quires (for 8, 16, and 32 bit posits) are available, but are poorly supported.

    Complex numbers

    The complex-herbie plugin has been removed as of Herbie 1.6. This plugin may be brought back in the future.

    Generic floating-point numbers

    The float-herbie plugin implements support for any IEEE-754 binary floating-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once float-herbie is installed, specify :precision (float ex nb) to inform Herbie that it should assume the core's inputs and outputs are floating-point numbers with ex exponent bits and nb total bits (sign bit + mantissa bits + exponent bits).

    Generic fixed-point numbers

    The fixedpoint-herbie plugin implements support for any fixed-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once fixedpoint-herbie is installed, specify :precision (fixed nb sc) to inform Herbie that it should assume the core's inputs and outputs are signed fixed-point numbers with nb total bits and a scaling factor of 2sc (integer formats have a scaling factor of 20). This plugin also supports unsigned fixed-point numbers specified by :precision (ufixed nb sc) and provides simpler aliases for integer formats with :precision (integer nb) and :precision (uinteger nb).

    Developing plugins

    The following is a guide to creating a Herbie plugin. Plugins are considered experimental and may change considerably between releases. If you run into issues, please write to the mailing list. Be sure to check out the built-in plugins in the Herbie repository before getting started.

    First Steps
    All plugins are implemented as Racket packages. The easiest way to initialize a new Racket package is to run

    raco pkg new pkg-name
    in a new folder. Make sure the folder name is the same as the package name! This will initialize a Racket package with all the necessary files. Read the official Racket documentation on the raco tool for more information.

    A single entry needs to be added to the package manifest stored in info.rkt, and add (define herbie-plugin 'name) to the bottom of the file where name is a unique symbol that doesn't conflict with other Herbie plugins. As a suggestion, this should just be the package name.

    Next, edit the main.rkt file by erasing everything except the language specifier on the first line, and add the line (require herbie/plugin). This gives the package access to the Herbie plugin interface. Optionally add the following for debugging purposes (eprintf "Loading pkg-name support...\n") directly after the require statement.

    Finally, run the following in the folder containing info.rkt and main.rkt:

    raco pkg install
    This should install your package and check for errors. Now everything is set up! Of course, your plugin is empty and doesn't add any useful features. If you added the debugging line in main.rkt, you should see the string when you run Herbie.

    Adding Features
    Now that you have an empty plugin, you can begin adding new functions, rewrite rules, and number representatons. The procedures exported by the Herbie plugin interface can be roughly divided into two categories: unique and parameterized. Whether or not you use the unique or parameterized half of the interface (or maybe both!) depends entirely on the number representation a feature is being implemented for. First, identify if your number representation is unique or parameterized. For example, if you are adding features for double precision (or rather binary64), the representation is unique. If you are adding features for a generic floating point format, say (float ebits nbits), then the representation is parameterized.

    Plugin Interface (Unique)
    The following are the signatures and descriptions of the plugin procedures for unique representations. These procedures are required to be at the top-level of main.rkt rather than inside a function.

    (define-type name (exact? inexact?) exact->inexact inexact->exact)
    Adds a new type with the unique identifier name. The arguments exact? and inexact? return true if a value is an exact or high-precision approximate representation. For Herbie's real type, exact? is implemented with real? and inexact? is implemented with bigfloat?. The procedures exact->inexact and inexact->exact convert between exact? and inexact? values.
    (define-representation (name type repr?) bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Adds a new representation with the unique identifier name. The representation will inherit all rewrite rules defined for type. By default, Herbie defines two types: real and bool. Your representation will most likely inherit from real. The width argument should be the bitwidth of the representation, e.g. 64 for binary64. The argument repr? is a procedure that accepts any argument and returns true if the argument is a value in the representation, e.g. an integer representation should use Racket's integer?, while special? takes a value in the representation and returns true if it is not finite, e.g. NaN or infinity.

    The other four arguments are single-argument procedures that implement different conversions. The first two convert between a value in your representation and a Racket bigfloat (you need to import math/bigfloat). The last two convert between a value in your representation and its corresponding ordinal value. Ordinal values for any representation must be within the interval [0, 2width - 1]. Check Racket's definition of ordinals for floats. Note that those ordinal values can be negative.
    (define-operator (name itype-names ...) otype-name
    [bf bf-fn]
    [ival ival-fn])
    Adds a new operator. Operators describe pure mathematical functions, i.e. + or sin. The parameters itype-names and otype-name are the input type(s) and output type names. For example, + takes two real inputs and produces one real output. The bf-fn argument is the bigfloat implementation of your operator. The ival-fn argument is the Rival implementation of your operator. This is optional but improves the quality of Herbie's output. If you don't want to implement this, set ival-fn to false. To define operators with an unknown number of arguments, e.g. comparators, add the attribute [itype itype]. This will override the input type names defined by itype-names. See the bottom of this section for support for constants.
    (define-operator-impl (op name irepr-names ...)orepr-name
    [fl fl-fn]
    ...)
    Implements op with input representation(s) irepr-names and output representation orepr-name. The field name must be unique. For example, Herbie implements +.f64 and +.f32 for double- and single-precision floats. The argument fl-fn is the actual procedure that does the computation. Like define-operator, the input representations can be overridden with [itype irepr]. By default, the attributes bf and ival are inherited from op but can be overridden as previously described. See the bottom of this section for support for constant implementations.
    (define-ruleset name (groups ...) #:type ([var repr] ...)
    [rule-name match replace]
    ...)
    Defines a set of rewrite rules. The name of the ruleset as well as each rule-name must be a unique symbol. Each ruleset must be marked with a set of groups (read here on ruleset groups). Each rewrite rule takes the form match ⇝ replace where Herbie's rewriter will replace match with replace (not vice-versa). Each match and replace is an expression whose operators are the names of operator implementations rather than pure mathematical operators. Any variable must be listed in the type information with its associated representation. See the softposit-herbie plugin for a more concrete example.
    (define-ruleset* name (groups ...) #:type ([var type] ...)
    [rule-name match replace]
    ...)
    Like define-ruleset, but it defines a ruleset for every representation that inherits from type. Currently, every type must be the same, e.g. all real, for this procedure to function correctly. Unlike define-ruleset, match and replace contain the names of operators rather than operator implementations.

    Procedures for declaring constants are not a part of the plugin interface. Instead, constants and constant implementations are defined as zero-argument operators and operator implementations. The fields fl-fn, bf-fn, and ival-fn should be implemented with zero-argument procedures (thunks). Similar to operator and operator implementations, constants describe pure mathematical values like π or e while constant implementations define an approximation of those constants in a particular representation.

    Plugin Interface (Parameterized)
    Defining operators, constants, and representations for parameterized functions requires a generator procedure for just-in-time loading of features for a particular representation. When Herbie encounters a representation it does not recognize (not explicitly defined using define-representation) it queries a list of generators in case the representation requires just-in-time loading.

    The following procedure handles represention objects:

    (get-representation name)
    Takes a representation name and returns a representation object. Do not call this function before the associated representation has been registered!

    The following procedures handle generators:

    (register-generator! gen)
    Adds a representation generator procedure to Herbie's set of generators. Representation generator procedures take the name of a representation and return the associated representation object if it successfully created the operators, constants, and rules for that representation. In the case that your plugin does not register the requested representation, the generator procedure need not do anything and should just return false.
    (register-conversion-generator! gen)
    Adds a conversion generator procedure to Herbie's set of generators. Conversion generator procedures take the names of two representations and returns true if it successfully registered conversion(s) between the two representations. Conversions are one-argument operator implementations of the cast operator that have one representation as an input representation and a different representation as an output representation. User-defined conversions are OPTIONAL for multi-precision optimization, since Herbie can synthesize these by default. However Herbie's implementations are often slow since they are representation-agnostic and work for any two representations. In the case that your plugin does not register the requested conversion(s), the generator procedure need not do anything and should just return false.

    To actually add representations, operators, etc. within a generator procedure, you must use a set of alternate procedures.

    (register-representation! name
    type
    repr?
    bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Like define-representation, but used within generators.
    (register-representation-alias! name repr)
    Adds an alias name for an existing representation repr. If two representations are equivalent, e.g. (float 8 32) and binary32, this procedure can be used to declare the two representations equivalent.
    (register-operator! op name itype-names otype-name attribs)
    Like define-operator, but used within generators. The argument itype-names is a list of the input types while the argument attribs are the same attributes for define-operator, e.g. bf. In this case, attribs is an association: (list (cons 'bf bf-fn) ...).
    (register-operator-impl! op name ireprs orepr attribs)
    Like define-operator-impl, but used within generators. Unlike define-operator-impl, this procedure takes representation objects rather than representation names for ireprs and orepr. Use get-representation to produce these objects. See register-operator! for a description of attribs.
    (register-ruleset! name groups var-repr-names rules)
    Like define-ruleset, but used within generators. In this case, groups is a list of rule groups; var-repr-names is an association pairing each variable in the ruleset with its representation, e.g. (list (cons 'x '(float 5 16)) ...); and rules is a list of rules of the following form (list (list rule-name match replace) ...).
    ================================================ FILE: www/doc/1.6/release-notes.html ================================================ Herbie 1.6 Release Notes

    Herbie 1.6 Release Notes

    The Herbie developers are excited to announce Herbie 1.6! This release focuses on further integration of egg, improved reliability, and a better web interface. Our favorite features are below.

    The Herbie team, working over Zoom to bring you Herbie 1.6

    What is Herbie? Herbie automatically improves the accuracy of floating point expressions. This avoids the bugs, errors, and surprises that so often occur with floating point arithmetic.

    Join us! Join the Herbie developers on the FPBench Slack, at the monthly FPBench community meetings, and at FPTalks 2022.

    Comparison of Herbie's total output error (bits) across 100 seeds with the old recursive rewriter (top) versus the egg-based implementation (bottom). The egg-based implementation has lower variability.

    Recursive Rewriting with egg

    Two releases ago, Herbie 1.4 featured a new simplifier that used the egg library for a substantial increase in speed. This release further incorporates the egg library into Herbie by replacing the recursive rewriter with an egg-based implementation. Herbie's output is now more stable across seeds compared to the previous implementation, maintains a similar level of performance, and increases accuracy gains overall. This change also makes it easier to add new rewrite rules, since the egg-based rewriter's behavior is more predictable than the old recursive rewriter was.
    Preconditions in the new web interface. For each input, users are asked to provide a range of values for Herbie to focus on optimizing over. Input ranges vary depending on the user's application and should always be supplied to maximize accuracy.

    Interactive Herbie

    Despite the fact that most users interact with Herbie via the demo page, the web interface has historically had a minimal design, with important features like preconditions hidden behind advanced configuration dialogs. As part of an ongoing push to make Herbie more user-friendly, we have added support for preconditions to the main interface, and have improved the display of warnings and errors. We expect the demo page to change further in the coming year to provide users with more support in analyzing Herbie's output and testing their own ideas for rewritings.

    Comparison of the midpoint (left) vs. shortest number (right) methods for selecting branch conditions.

    Shorter Branch Conditions

    Herbie now synthesizes branch conditions with shorter split values. Before, Herbie's binary search algorithm would narrow down the set of possible split values to a small interval from which Herbie took the midpoint. Often the midpoint had a long string representation which made it seem like it was chosen with high precision. Now Herbie will choose a value on that same interval with a short string representation. This change makes output programs more readable and highlights the low precision in the result of binary search.

    System diagram for Herbie 1.6, with key changes in red dotted boxes. From top to bottom: double-precision (binary64) and single-precision (binary32) types are loaded through plugins instead of being embedded in Herbie's core; a new patching subsystem bundles together the various rewriting methods behind a simple interface; recursive rewrite uses egg. Compare Herbie 1.5 and 1.6 system diagrams here and here.

    Patching and Plugins

    Herbie has undergone a significant architectural change since the previous release. Although this change may not be visible to users, we hope that it makes future Herbie development more streamlined and provides a clearer answer to the question: what is Herbie? In particular, two major improvements include adding the "patch table" and moving number system specifics out of Herbie's core architecture.

    The patch table manages most rewriting capabilities, provides a minimal interface to generate variants from a set of expressions, and caches variants in case of duplicate input expressions. Creating this subsystem to handle variant generation more cleanly separates the "generate" and "test" parts within Herbie.

    Herbie's double-precision and single-precision representations are now implemented as plugins that automatically ship with Herbie. Representation-specific operators and definitions are no longer present in Herbie's core architecure. This change makes Herbie representation-agnostic and loads double- and single-precision representations through the plugin interface. Not only is this design cleaner, but these plugins now serve as examples for plugin developers. In the future, we hope to move additional definitions out of core Herbie and into plugins such as error metrics and cost models (Pherbie).


    Other improvements

    • Precondition analysis and point sampling are unified under a single function. Sampling multiple functions is now supported.
    • The backup sampler now just computes with MPFR floats at high-precision rather than the old "halfpoints" sampler.
    • Constants are now represented internally as operators. This simplifies the plugin interface as well as Herbie's internals.
    • Support for variary operators has been removed. Relational operators are now expanded when parsed.
    • Constants are always read as exact rational numbers. This enables additional optimizations through constant folding.
    • More output languages are supported in the reports including Fortran, Java, Python, Julia, MatLab, and Wolfram.
    • egg has been updated to 0.8.1 which has led to a performance increase. Make sure to have at least Rust 1.60 when installing from source.

    Deprecations and removals

    • The Bessel functions j0, j1, y0, and y1 have been deprecated and will be removed in the next release.
    • Support for the Racket 7.x has been removed. Please upgrade.
    • Support for complex numbers has been removed. Please let us know if you are using it. We hope to reimplement this eventually, but with a better architecture.
    • The precision:fallback flag has been deprecated, and no longer does anything. Use the :precision racket flag instead.
    • Support for regraph has been removed.

    Try it out!

    We want Herbie to be 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!

    ================================================ FILE: www/doc/1.6/report.html ================================================ Herbie reports

    Herbie reports

    The Herbie report

    When used in the browser, Herbie generates HTML reports full of information about the accuracy of your input and its output expressions.

    Summary numbers

    First, a brief summary of the results. For most uses, the “Average Error” number, which describes how accurate the input and output expressions are, is the most important statistic in this section. The other numbers list time Herbie took to improve the program and the precision assumed for floating-point operations.

    Summary numbers from a Herbie report. The "binary64" precision refers to double-precision IEEE-754 arithmetic.

    Input and output programs

    Second, the input and output programs themselves, in standard mathematical syntax. In the top-right corner, the drop-down can be used to switch to C syntax or some other format.

    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. You can add a precondition to restrict Herbie to certain inputs in those cases.

    These graphs show the error of the input program with a red line, and the error of the output program with a blue line. Both can be toggled. If the expression has multiple variables, the variable picker on the bottom left selects which variable is placed on the horizontal axis. 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).

    Interactive inputs

    Fourth, a interactive form where you can the output of both your and Herbie's programs. on inputs of your choice Enter argument values on the left, and the input and output programs will be evaluated on those arguments and the results printed on the right.

    The interactive section of a Herbie report. "In" is your program, "Out" is Herbie's.

    Derivation

    Fifth, Herbie's derivation of its output program. These can be helpful in understanding how Herbie works. Each substantive step in the derivation also lists the error, in bits, of that step's output. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program.

    Derivations sometime name arithmetic laws used by Herbie, or they might claim derivation steps are done by simplification, series expansion, or other Herbie strategies. The derivation will also call out any time the input is split into cases. When one part of the program is colored blue, that is the part modified in that derivation step.

    A short derivation from a Herbie report. Note the error, in gray, measured after each derivation step.

    Reproduction

    Sixth, a command to reproduce this Herbie result. If you find a Herbie bug, include this code snippet when filing an issue.

    Reproduction information for a Herbie run. You can use this when submitting bug reports.

    The top of the page has a right-hand menu bar with additional links. “Report” returns you to Herbie's main page. “Log” and “Metrics” give you detailed internal information about Herbie.

    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.

    ================================================ FILE: www/doc/1.6/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/1.6/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 for a computer. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using Herbie.

    Giving Herbie expressions

    Start Herbie with:

    herbie web

    After a brief wait, this will open your web browser and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Type (1 + x) - x into this box. You should see two boxes appear for entering the lower and upper bound on the range of values for x. Select the lowest and highest options, like this:

    The input range field in the Herbie web UI.
    Finally, hit the "Improve with Herbie" button. You should see the entry box gray out, then some text will appear on the screen describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results. The most important part of that page is the large gray box in the middle:

    Input and output program from a Herbie report.

    It shows the input, (1 + x) - x, and Herbie's more accurate way to evaluate that expression: 1. Herbie did a good job, which you can see from the statistics at the top of the page:

    Statistics and error measures for this Herbie run.

    The initial program had 29.0 bits of error (on average), while Herbie's better version had 0 bits of error. That's because (1 + x) - x should always be exactly equal to 1, but in floating-point arithmetic, when x is really big, 1 + x rounds down to x and the expression returns 0.

    There is more information on this results web page, which you can use explain more about the expression's errors and how Herbie derived its result.

    Programming with Herbie

    Now that you've run Herbie and know how to read its results, let's apply Herbie to a realistic program.

    Herbie's input expressions can come from source code, mathematical models, or even a debugging tool like Herbgrind. But most often, they come from your mind, while you're writing new mathematical code.

    When you're writing a new numerical program, it's best to keep Herbie open in a browser tab so you can run it easily. That way, you can run Herbie on any complex floating-point expression you're coding up and so always use an accurate version of that expression. Herbie has options to log all the expressions you enter, so that you can refer to them later.

    However, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small core that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or print results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. The bug we're interested in is about complex square root, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, five different number types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total this code turns into four Herbie inputs: two output fields, for each of two branches.

    Let's focus on first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative (but it runs for all values of x.re). So, select the full range of values for x.re, but restrict the range of x.im, like this:

    Restricting the input range to xim >= 0.
    This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression.

    Using Herbie's results

    Herbie will churn for a few seconds and produce an output, perhaps something like this:

    Herbie's version of the complex square root expression.

    Herbie's algorithm is randomized, so you likely won't see the exact same thing. For example, the branch expression xre ≤ -4.780438341784697e-111 will probably have some other really small number. And perhaps Herbie will choose slightly different expressions. But the result should be recognizably similar. In this case, Herbie reports that the initial expression had 38.7 bits of error, and that the output has 29.4.

    It's a little harder to describe what Herbie found wrong with the original expression, and why its new version is better—it is due to a floating-point phenomenon called “cancellation”. But you can get some insight from the error plot just below the program block. Select the xim variable just below the plot, and you will see something like this:

    Herbie's error plot for the complex square root expression.

    There's a lot going on here. Along the horizontal axis, you have the various values of xim. Note that the graph is log-scale, and includes only non-negative values thanks to our precondition. The value 1 is in the middle; to the left are values with small exponents close to zero, and to the right you have values with large exponents approaching infinity.

    The vertical axis measures bits of error, from 0 to 64. Lower is better. There are two lines drawn: a red one for the original expression and a blue one for Herbie's version. You can see from the plot that as xim gets smaller (toward the left, closer to zero), Herbie's improvement becomes more and more significant. You can also see that for very large values of xim, the original program had maximal error (in fact, it overflows) but the Herbie's version is better, though not great.

    Of course, your exact output will differ a bit from the screenshots and descriptions here, because Herbie is randomized.

    Now that you have the more accurate version of this expression, all you need to do is insert it back into the program:

    var r = Math.sqrt(x.re * x.re + x.im * x.im);
    // Herbie version of 0.5 * Math.sqrt(2.0 * (r + x.re))
    var re;
    if (x.re <= -4.780438341784697e-111) {
        re = Math.abs(x.im) * Math.sqrt(0.5) / Math.sqrt(r - x.re);
    } else if (x.re <= 1.857088496624289e-105) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else if (x.re <= 117.16871373388169) {
        re = 0.5 * Math.sqrt(2.0 * (r + x.re));
    } else if (x.re <= 5.213930590364927e+88) {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.im));
    } else {
        re = 0.5 * Math.sqrt(2.0 * (x.re + x.re));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the original code in place in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, like C, you can use the drop-down in the top-right corner of the gray program block to translate Herbie's output to that language.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/1.6/using-cli.html ================================================ Using Herbie from the Command Line

    Using Herbie from the Command Line

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    herbie shell
    Herbie 1.4 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore
      (x)
      ...
      1.0)

    Herbie suggests that 1 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell makes it easy to play with different expressions and try multiple variants, informed by Herbie's advice.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie on 3 problems (seed: 1809676410)...
      1/3   [   1.799s]   39→ 0     Expanding a square
      2/3   [   3.256s]    0→ 0     Commute and associate
      3/3   [   0.937s]   29→ 0     Cancel like terms

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

    ;; seed: 1809676410
    
    (FPCore (x) ... 1.0)
    (FPCore (x) ... (* x (+ x 2.0)))
    (FPCore (x y z) ... 0.0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/1.6/using-web.html ================================================ Using Herbie from the Browser

    Using Herbie from the Browser

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie. Run the Herbie web shell with this command:

    herbie web

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

    herbie web
    Herbie 1.6 with seed 841489305
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    Type expressions in standard mathematical syntax (parsed by Math.js). For each input variable, specify the range of values that Herbie should consider when trying to improve the expression. Hit the "Improve with Herbie" button to have Herbie attempt to improve the expression.

    Herbie shows improvement logs as it works.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    Batch report generation

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

    herbie report bench/tutorial.fpcore output/
    Starting Herbie on 3 problems (seed: 1809676410)...
      1/3   [   1.799s]   39→ 0     Expanding a square
      2/3   [   3.256s]    0→ 0     Commute and associate
      3/3   [   0.937s]   29→ 0     Cancel like terms

    This command asks Herbie to generate a report from the input expressions in bench/tutorial.fpcore and to save the report in the directory output/. It's best if that directory doesn't exist before running this command.

    Once generated, open output/results.html in your favorite browser (but see the FAQ if you're using Chrome). That page summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see their report.

    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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/2.0/api-endpoints.html ================================================ Herbie HTTP API Endpoints

    HTTP API

    The Herbie API allows applications to interface with Herbie using HTTP requests. The API is designed to be stateless: the order in which endpoints are called shouldn't matter.

    Format for all endpoints

    All the endpoints listed below respond to POST requests unless otherwise specified. A typical example of sending a POST request to a running Herbie server is:

    curl -X POST -d \
      '{"formula": "(FPCore (x) (- (sqrt (+ x 1))))",\
        "seed": 5})}' \
      -H 'Content-Type: application/json' \
      http://127.0.0.1:8000/api/sample
        

    /api/sample

    Example input & output

    Request:

    {
            formula: <FPCore expression>,
            seed: <random seed for point generation>
          }

    Response:

    {
            points: [[point, exact], ... ]
          }

    The sample endpoint allows the user to request a sample of points given the FPCore expression and a seed.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/exacts

    Example input & output

    Request:

    {
            formula: <FPCore expression>,
            sample: [point, ... ]
          }

    Response:

    {
            points: [[point, exact], ... ]
          }

    The exacts endpoint allows the user to request the exact value of a set of points evaluated at a real number specification given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/calculate

    Example inputs & outputs

    Request:

    {
            formula: <FPCore expression>,
            sample: [point ... ]
          }

    Response:

    {
            points: [[point, exact], ... ]
          }

    The calculate endpoint allows the user to request the evaluation of a set of points evaluated at a floating-point implementation given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the evaluation of each point using the given FPCore as a floating-point implementation. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the evaluated value; the evaluated value of point n is points[n][1].

    /api/analyze

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      points: [[point, error], ... ]
    }

    The analyze endpoint allows the user to request error analysis of a set of point-exact pairs and a given floating-point implementation.

    Given a collection of points, their exact values, and an FPCore expression to analyze on, the analyze endpoint returns the error for each point for that expression. The error value returned is Herbie's internal error heuristic.

    /api/alternatives

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      alternatives: [alt, ... ],
      histories: [history, ... ],
      splitpoints: [splitpoint, ... ]
    }

    The alternatives endpoint allows the user to request rewrites from Herbie given an expression to rewrite and a set of point-exact pairs.

    Returns a list of alternatives represented by FPCore expressions through the "alternatives" field.

    Returns a list of derivations of each alternative through the "histories" field where history[n] corresponds to alternatives[n].

    Returns a list of splitpoints for each alternative through the "splitpoints" field where splitpoints[n] corresponds to alternatives[n]. splitpoints[n] will only contain information about the corresponding alternative.s splitpoints if the alternative is a branch-expression.

    /api/mathjs

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>
    }

    Response:

    {
      mathjs: <mathjs expression>
    }

    The mathjs endpoint allows the user to translate FPCore expressions into mathjs expressions.

    /api/local-error

    Forthcoming.

    /api/cost

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        cost: [cost]
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: [13120]
    }

    Lower-Cost Example: (x+1)-(x)

    Request:

    {
        formula: <(FPCore (x) (- (+ x 1 ) x))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: [320]
    }

    The cost endpoint allows the user to request the evaluation of an expression's overall cost. Cost is calculated depending on the complexity of operations contained in the expression.

    Given an FPCore expression and a collection of points, returns the cost of the expression. The cost value returned is calculated internally by Herbie.

    The points should be of the same format as the points generated in the sample endpoint. Refer to /api/sample for more details.

    Note the sample points are not used in the cost calculation. The contents of the points do not matter as long as they are in the correct format as mentioned above.

    /api/translate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        language: "language"
    }

    Response:

    {
        result: "translated expression"
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        language: "python"
    }

    Response:

    {
        result: "def expr(x): return math.sqrt((x + 1.0)) - math.sqrt(x)"
    }

    The translate endpoint allows users to translate FPCore expressions into equivalent expressions in various programming languages.

    Given an FPCore expression and a target language, this endpoint returns the translated expression in the specified language. The language parameter should be a string representing the desired programming language. The response includes the translated expression.

    Currently supported languages are: python, c, fortran, java, julia, matlab, wls, tex, and js.

    ================================================ FILE: www/doc/2.0/diagrams.html ================================================ Diagrams

    Diagrams

    System diagram of Herbie

    High-level system diagram of Herbie. It highlights Herbie's core architecture, external libraries, and user interactions. Basic flow: Herbie passes user input (expression, precondition, etc.) to the mainloop (scheduler) which alternates between generate and test phases multiple times, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-test phase is complete, Herbie extracts either one or many output expressions using an algorithm called regime inference. Regime inference chooses the "best" (usually most accurate) generated candidate expression or combines multple candidates, each "best" on a smaller part of the input range, with a branch condition.

    ================================================ FILE: www/doc/2.0/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    Herbie can also be installed from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker commands with sudo or run them as the administrative user.

    With Docker installed, you can run the Herbie shell with:

    docker run -it uwplse/herbie shell

    This will download the Herbie image and then run its shell tool.

    Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web interface

    You can run the Herbie web server locally with

    docker run -it --rm -p 8000:80 uwplse/herbie
    and access the server at http://localhost:8000.

    (Herbie's Docker image binds to port 80 by default; this command uses the -p <hostport>:80 option to expose Herbie on port 8000.)

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    Building the Docker image

    This section is primarily of interest for the Herbie developers.

    Clone the repo and confirm that Herbie builds correctly with make install.

    Next, examine the Dockerfile and Makefile together. The Dockerfile should follow a process exactly like the Makefile, except a clean initial environment is assumed. The build may be split into 2 or more stages to limit the size of the resulting image. Each stage consists of a FROM command and a series of further commands to build up the desired environment, and later stages can refer to earlier stages by name—for example, COPY --from=earlier-stage ... can copy files compiled in earlier images. You may need to do things like bumping the version of Rust used for binary compilation or the version of Racket used in production, or adjusting paths to match the newest version of the repo.

    Once you are ready to build, run this command from the repository root:

    docker build -t uwplse/herbie:test .

    This builds a new test image and tags it uwplse/herbie:test. You can run this image with:

    docker run -p 8000:80 -it uwplse/herbie:test

    The web demo should now be visiable at http://localhost:8000.

    To open a shell in a running container for testing, first get the container ID with:

    docker ps

    Then open a root shell in that container with

    docker exec -it <CONTAINER ID> sh

    The code and egg-herbie binaries should be under /src.

    ================================================ FILE: www/doc/2.0/error.html ================================================ Herbie Input Format

    What is Error?

    Herbie helps you identify and correct floating point error in your numeric programs. But what is floating point error, and how does Herbie measure it?

    The summary

    When Herbie reports a "percentage accuracy" number like 92.3%, it's usually best to think of that as the percentage of floating-point inputs where the expression is reasonably accurate.

    The impact of this error on your application will depend a lot on which 7.7% of inputs are inaccurate, and what kind of error that is. You can find this out using the accuracy graph in Herbie reports. You can also use preconditions to restrict the inputs Herbie is considering.

    Why rounding matters

    In mathematics, we work with real numbers, but on a computer, we typically use floating-point numbers. Because there are infinitely many real numbers, but only finitely many floating-point numbers, some real numbers can't be accurately represented. This means that every time you do an operation, the true result typically has to be rounded to a representable one.

    Take an extreme example: the code 1e100 + 1, which increments a huge number in IEEE 754 double-precision floating-point. There's an exact real-number answer, a one followed by 99 zeros and then another 1, but the closest floating-point value is the same as 1e100.

    Errors like this can cause problems. In the example above, the answers differ by one part per googol, which is pretty small. But the error can grow. For example, since 1e100 + 1 rounds to the same value as 1e100, the larger computation

    (1e100 + 1) - 1e100
    returns 0 instead of the correct answer, 1. Now the difference is pretty stark, and can grow even bigger through later operations.

    Bits of error

    There are lots of ways to measure how much rounding error there is in a computation. Most people find the notions of absolute and relative error most intuitive, but Herbie internally uses a more complex notion called bits of error.

    The bits of error metric imagines you have a list of all of the possible floating-point numbers, from largest to smallest. In that list, compare the floating-point value you computed to the one closest to the true answer. If they are the same, that's called 0 bits of error; if they are one apart, that's called one bit of error; three apart is two bits of error; and so on.

    In general, if the two floating-point values are n items apart, Herbie says they have log2(n + 1) bits of error. Values like 0 and -0 are treated as having 0 bits of error, and NaN is considered to have the maximum number of bits of error against non-NaN values. While there's all sorts of theoretical justifications, Herbie mainly uses this error metric because we've found it to give the best results.

    On a single input, the best way to interpret the "bits of error" metric is that it tells you roughly how many bits of the answer, starting at the end, are useless. With zero bits of error, you have the best answer possible. With four bits, that's still pretty good because it's four out of 64. But with 40 or 50 bits of error, you're getting less accuracy out of the computation than even a single-precision floating-point value. And it's even possible to have something like 58 or 60 bits of error, in which case even the sign and exponent bits (which in double-precision floating-point occupy the top 12 bits of the number) are incorrect.

    Percentage accuracy

    Because different number representations have different numbers of bits, Herbie shows the percentage of bits that are accurate instead of the bits of error. With double-precision floats, for example, 75% accurate means 16 bits of error.

    Bits of error measures the error of a computation for some specific input. But usually you're interested in the error of a computation across many possible inputs. Herbie therefore averages the accuracy percentage across many randomly-sampled valid inputs.

    Typically, inputs points are either very accurate or not accurate at all. So when computing percentage accuracy, Herbie's averaging a lot of points with near-100% accuracy and a lot of points with near-0% accuracy. In that sense, you can think of percentage accuracy as measuring mostly what percentage of inputs are accurate. If Herbie says your computation is 75% accurate what it's really saying is that about a quarter of inputs produce usable outputs.

    Valid inputs

    Since percentage accuracy averages over many points, it depends on what points it is averaging over, and how is samples among them. Herbie samples among valid inputs, uniformly distributed.

    Herbie considers an input valid if it is a floating-point value in the appropriate precision and its true, real-number output 1) exists; 2) satisfies the user's precondition; and 3) can be computed. Let's dive into each requirement.

    1. An output can fail to exist for an input if that input does something like divide by zero or take the square root of a negative number. Then that input is invalid.
    2. An input can fail to satisfy the user's precondition. Preconditions can be basically anything, but most often they specify a range of inputs. For example, if the precondition is (< 1 x 2), then the input x = 3 is invalid.
    3. Finally, and most rarely, Herbie can fail to compute the output for a particular input. For example, the computation (/ (exp 1e9) (exp 1e9)), which divides two identical but gargantuan numbers, does have an exact real-number answer (one), but Herbie can't compute that exact answer because the intermediate steps are too large. This input is also invalid.

    Herbie's percentage accuracy metric only averages over valid points. This means that when you change your precondition, you change which points are valid, and that can change the percentage accuracy reported by Herbie. This is useful: if you know there's a rare error, but Herbie thinks error is low, you can change your precondition to focus on the points of interest.

    Sampling inputs

    When randomly sampling inputs, Herbie considers each valid input equally likely. Importantly, this does not mean that it uses a uniform distribution, because floating-point values themselves are not uniformly distributed.

    For example, there are as many floating-point values between 1 and 2 as there are between one and one half, because floating-point values use an exponential encoding. But that means that if you're looking at a computation where the input comes from the interval [0.5, 2], Herbie will weight the first third of that range twice as high as the other two thirds.

    This can produce unintuitive results, especially for intervals that cross 0. For example, in the interval [0, 1], the second half of that interval (from one half to one) has a tiny proportion of the weight (in double-precision floating-point, about 0.1%). If Herbie can improve accuracy by a little bit between zero and one half, while dramatically reducing accuracy between one half and one, it will think that that's an accuracy improvement. You might disagree.

    Unfortunately, there's no way for Herbie to intuit exactly what you mean, so understanding this error distribution is essential to understanding Herbie's outputs. For example, if Herbie reports that accuracy improved from 75% to 76%, it's essential to know: is the improvement happening on inputs between one half and one, or between 1e-100 and 2e-100? To answer this question, it's important to look over the reports and graphs generated by Herbie.

    ================================================ FILE: www/doc/2.0/faq.html ================================================ Herbie FAQ

    Common Errors and Warnings

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors, warnings, and known issues.

    Common errors

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

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized. (- (exp x) 1) would be the correct way to write that expression. The input format documentation has more details on Herbie's syntax.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. The simplest fix is to increase the --num-analysis flag. Specifying the range of valid points as a precondition can also help.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. The solution is to fix the precondition or input program.

    Common warnings

    Herbie warnings refer to this section for explanations and common actions to take.

    Infinite outputs for N% of points.

    Sometimes, an input to your expression produces an output so large that it's best represented by a floating-point infinity. For example, (exp 1000) is over 10434, so it's much larger than the largest floating-point value. Herbie raises this warning when too many inputs (more than 20% of them) are this large, because that usually indicates you should set a more restrictive precondition.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10,000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large numbers would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Could not uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a string representation for a given value. This is likely the result of a bug in a third-party plugin.

    Unsound rule application detected

    Herbie uses a set of algebraic rewrite rules in order to simplify expressions, but these rules can sometimes lead to a contradiction. Herbie will automatically compensate for this, and in most cases nothing needs to be done. However, Herbie may have failed to simplify the output.

    Unused variable var

    The input FPCore contains a variable that is not used in the body expression.

    Strange variable var

    The input expression contains a variable that is similar in name to named constants, e.g. e instead of E.

    Known bugs

    Bugs that cannot be directly fixed are documented in this section.

    Missing reports chart on Chrome

    When using Chrome to view web pages on your local machine, Herbie reports cannot draw the arrow chart due to security restrictions. Run Chrome with --allow-file-access-from-files to fix this error.

    ================================================ FILE: www/doc/2.0/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    Math format

    The Herbie web shell takes input in a subset of the math.js syntax. The web shell automatically checks for syntax errors, and provides a graphical user interface for specifying the input domain. The web shell converts the mathematical expression and input ranges into FPCore before sending it to Herbie.

    FPCore format

    Herbie's command-line and batch-mode tools use FPCore format to describe mathematical expressions. FPCore looks like this:

    (FPCore (inputs ...) properties ... expression)
    (FPCore name (inputs ...) properties ... expression)

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context. If name is specified, the function can be referenced in subsequent FPCore declarations in the file.

    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))))

    The semicolon (;) character introduces a line comment. 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 include:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. The arithmetic operators associate to the left, and - is used for both subtraction and negation.

    Herbie links against your computer's libm to evaluate these functions. So, each function has the same behavior in Herbie as in your code. You can instead use the racket precision if you instead want reproducible behavior.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison functions support chained comparisons with more than two arguments.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    Unless you have a lot of Lisp experience, you'll probably find let* more intuitive.

    Internally, Herbie treats intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy. Using intermediate variables will therefore not produce a more accurate result or help Herbie run faster.

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will only use the :spec expression to evaluate error, not to search for accurate expressions.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point
    racket
    Like binary64, but using Racket math functions rather than your computer's libm.

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Herbie won't produce mixed-precision code unless you allow it to do so, using the :herbie-conversions property:

    :herbie-conversions
    ([prec1 prec2] ...)
    Expressions in precision prec1, can be rewriten to use precision prec2, and vice versa.

    All conversions are assumed to be bidirectional. For example, if you specify :herbie-conversions ([binary64 binary32]), Herbie will be able to add conversions between single- and double-precision floating-point.

    Miscellaneous Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie's output provide additional information in custom properties:

    :herbie-status status
    status describes whether Herbie worked: it 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-error-input
    ([pts err] ...)
    The average error of the input program at pts points. Multiple entries correspond to Herbie's training and test sets.
    :herbie-error-output
    ([pts err] ...)
    The computed average error of the output program, similar to :herbie-error-input.

    Herbie's benchmark suite also uses properties such as :herbie-target for continuous integration, but these are not officially supported and their use is discouraged.

    ================================================ FILE: www/doc/2.0/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows (though, on Windows, you will need to install Make). It can be installed from a package or from source. To start, install Racket, which Herbie is written in. Then install Herbie. (Herbie can also be installed from source or via a Docker image.)

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.0 are supported, but more recent versions are faster.

    Please note: on Linux, we recommend the official Racket installer over installing Racket via Snap. If you must install Racket from Snap, make sure Herbie and all packages are located in your home directory or another allow-listed directory.

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

    racket
    Welcome to Racket v8.9 [cs].
    > (exit)

    Installing Herbie from a package

    This installation method supports Windows, macOS, and Linux for x86-64. It won't work for ARM computers, including recent Apple computers. You may also need a working version of make; on Windows, that might have to be installed separately.

    First install Racket, as above. Then install Herbie from a package with:

    raco pkg install --auto herbie

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path (example paths for Racket 8.9):

    • On Windows, AppData\Roaming\Racket\8.9\bin in your user folder.
    • On macOS, Library/Racket/8.9/bin in your home folder.
    • On Linux, .local/share/racket/8.9/bin in your home directory.

    You can run herbie from that directory, or add it to your executable path. Please note that the path of the binary will be different on Linux if you have opted to use Snap. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie from source

    Installing Herbie from source is best if you plan to develop Herbie or Herbie plugins, or if you are on an ARM machine.

    Install Racket. Then install Rust, using rustup or via some other means. Versions as old as 1.60.0 are supported.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

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

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other directories. Install Herbie on your system with:

    make install

    This command installs Herbie and its dependencies, compiles it for faster startup, and places the herbie executable in your Racket user path, just like the package installation. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like a virtual machine. Herbie in Docker is more limited; we do not recommend using Herbie through Docker without prior Docker experience.

    The Docker documentation describes how to install and run the official uwplse/herbie image.

    ================================================ FILE: www/doc/2.0/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    Herbie commands

    There are a few different methods for interacting with Herbie, which we call tools. For example, Herbie can be run both interactively or in batch mode, and can generate output intended for the command line or for the web. Herbie provides four of these tools as subcommands:

    herbie web
    Use Herbie through your browser. The herbie web command runs Herbie on your local machine and opens its main page in your browser.
    herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie will print its more-accurate version.
    herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. These pages can be viewed in any browser (though with a quirk for Chrome).

    We recommend using report and web, which produce reports with lots of information about floating-point accuracy, including graphs of input values versus error and plots comparing cost and accuracy. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie tool --help to view available command-line options for a tool. This command also shows 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, like this:

    herbie report --timeout 60 in.fpcore out/

    Arguments cannot go before options.

    --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. Seeds are not preserved across runs.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of times Herbie attempts to improve accuracy. The default, 4, suffices for most programs and helps keep Herbie fast; in practice iterations beyond the first few rarely lead to lower error. Increase this option, say to 6, to check that there aren't further improvements that Herbie could seek out.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 12. Increasing this option will slow Herbie down, but may fix the "Cannot sample enough valid points" error.
    --num-enodes N
    The number of equivalence graph nodes to use when doing algebraic reasoning. The default is 8000. Increasing this option will slow Herbie down, but may improve results slightly.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 of the hardware threads.
    --no-pareto
    Disables cost-aware search. Herbie will only return a single program, which optimizes exclusively for accuracy. Herbie will be slightly faster as a result.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.). Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate but more confusing programs.

    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
    boolsBoolean operator identities
    branchesif statement simplification
    specialThe gamma, Bessel, and error functions
    numericsNumerical compounds expm1, log1p, fma, and hypot

    Search options

    These options change the types of transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    setup:search
    This option, on by default, uses interval subdivision search to help compute ground truth for complicated expressions. If turned off, Herbie will be slightly faster, but may hit the "Cannot sample enough valid points" error more often. Instead of turning this option off, you may want to adjust the --num-analysis flag instead.
    setup:simplify
    This option, on by default, simplifies the expression before starting the Herbie improvement loop. If turned off, Herbie's results will likely be somewhat worse, but its output program may be more similar to the input program.
    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 Herbie's creativity.
    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. If turned off, Herbie's results will more likely be algebraically equivalent to the input expression, but may be less accurate.
    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.
    generate:proofs
    This option, on by default, generates step-by-step derivations for HTML reports. If turned off, the step-by-step derivations will be absent, and Herbie will be slightly faster.
    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). Turn this option off if your programming environment makes branches very expensive, such as on a GPU.
    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. 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 inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.
    ================================================ FILE: www/doc/2.0/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie plugins define new functions, add rewrite rules, and even implement number representations.

    Herbie plugins are installed separately. Herbie then automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    This plugin uses the SoftPosit library, only supported on Linux. Even then is reported to misbehave on some machines. The plugin support arithmetic operations, sqrt, and quires.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (with 8 or 32 bits) and also quires (for 8, 16, and 32 bit posits) are available, but are poorly supported.

    Complex numbers

    The complex-herbie plugin has been removed as of Herbie 1.6. This plugin may be brought back in the future.

    Generic floating-point numbers

    The float-herbie plugin implements support for any IEEE-754 binary floating-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once float-herbie is installed, specify :precision (float ex nb) to inform Herbie that it should assume the core's inputs and outputs are floating-point numbers with ex exponent bits and nb total bits (sign bit + mantissa bits + exponent bits).

    Generic fixed-point numbers

    The fixedpoint-herbie plugin implements support for any fixed-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once fixedpoint-herbie is installed, specify :precision (fixed nb sc) to inform Herbie that it should assume the core's inputs and outputs are signed fixed-point numbers with nb total bits and a scaling factor of 2sc (integer formats have a scaling factor of 20). This plugin also supports unsigned fixed-point numbers specified by :precision (ufixed nb sc) and provides simpler aliases for integer formats with :precision (integer nb) and :precision (uinteger nb).

    Developing plugins

    The following is a guide to creating a Herbie plugin. Plugins are considered experimental and may change considerably between releases. If you run into issues, please file a bug. Be sure to check out the built-in plugins in the Herbie repository before getting started.

    First Steps
    All plugins are implemented as Racket packages. The easiest way to initialize a new Racket package is to run

    raco pkg new pkg-name
    in a new folder. Make sure the folder name is the same as the package name! This will initialize a Racket package with all the necessary files. Read the official Racket documentation on the raco tool for more information.

    A single entry needs to be added to the package manifest stored in info.rkt, and add (define herbie-plugin 'name) to the bottom of the file where name is a unique symbol that doesn't conflict with other Herbie plugins. As a suggestion, this should just be the package name.

    Next, edit the main.rkt file by erasing everything except the language specifier on the first line, and add the line (require herbie/plugin). This gives the package access to the Herbie plugin interface. Optionally add the following for debugging purposes (eprintf "Loading pkg-name support...\n") directly after the require statement.

    Finally, run the following in the folder containing info.rkt and main.rkt:

    raco pkg install
    This should install your package and check for errors. Now everything is set up! Of course, your plugin is empty and doesn't add any useful features. If you added the debugging line in main.rkt, you should see the string when you run Herbie.

    Adding Features
    Now that you have an empty plugin, you can begin adding new functions, rewrite rules, and number representatons. The procedures exported by the Herbie plugin interface can be roughly divided into two categories: unique and parameterized. Whether or not you use the unique or parameterized half of the interface (or maybe both!) depends entirely on the number representation a feature is being implemented for. First, identify if your number representation is unique or parameterized. For example, if you are adding features for double precision (or rather binary64), the representation is unique. If you are adding features for a generic floating point format, say (float ebits nbits), then the representation is parameterized.

    Plugin Interface (Unique)
    The following are the signatures and descriptions of the plugin procedures for unique representations. These procedures are required to be at the top-level of main.rkt rather than inside a function.

    (define-type name (exact? inexact?) exact->inexact inexact->exact)
    Adds a new type with the unique identifier name. The arguments exact? and inexact? return true if a value is an exact or high-precision approximate representation. For Herbie's real type, exact? is implemented with real? and inexact? is implemented with bigfloat?. The procedures exact->inexact and inexact->exact convert between exact? and inexact? values.
    (define-representation (name type repr?) bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Adds a new representation with the unique identifier name. The representation will inherit all rewrite rules defined for type. By default, Herbie defines two types: real and bool. Your representation will most likely inherit from real. The width argument should be the bitwidth of the representation, e.g. 64 for binary64. The argument repr? is a procedure that accepts any argument and returns true if the argument is a value in the representation, e.g. an integer representation should use Racket's integer?, while special? takes a value in the representation and returns true if it is not finite, e.g. NaN or infinity.

    The other four arguments are single-argument procedures that implement different conversions. The first two convert between a value in your representation and a Racket bigfloat (you need to import math/bigfloat). The last two convert between a value in your representation and its corresponding ordinal value. Ordinal values for any representation must be within the interval [0, 2width - 1]. Check Racket's definition of ordinals for floats. Note that those ordinal values can be negative.
    (define-operator (name itype-names ...) otype-name
    [bf bf-fn]
    [ival ival-fn])
    Adds a new operator. Operators describe pure mathematical functions, i.e. + or sin. The parameters itype-names and otype-name are the input type(s) and output type names. For example, + takes two real inputs and produces one real output. The bf-fn argument is the bigfloat implementation of your operator. The ival-fn argument is the Rival implementation of your operator. This is optional but improves the quality of Herbie's output. If you don't want to implement this, set ival-fn to false. To define operators with an unknown number of arguments, e.g. comparators, add the attribute [itype itype]. This will override the input type names defined by itype-names. See the bottom of this section for support for constants.
    (define-operator-impl (op name irepr-names ...)orepr-name
    [fl fl-fn]
    ...)
    Implements op with input representation(s) irepr-names and output representation orepr-name. The field name must be unique. For example, Herbie implements +.f64 and +.f32 for double- and single-precision floats. The argument fl-fn is the actual procedure that does the computation. Like define-operator, the input representations can be overridden with [itype irepr]. By default, the attributes bf and ival are inherited from op but can be overridden as previously described. See the bottom of this section for support for constant implementations.
    (define-ruleset name (groups ...) #:type ([var repr] ...)
    [rule-name match replace]
    ...)
    Defines a set of rewrite rules. The name of the ruleset as well as each rule-name must be a unique symbol. Each ruleset must be marked with a set of groups (read here on ruleset groups). Each rewrite rule takes the form match ⇝ replace where Herbie's rewriter will replace match with replace (not vice-versa). Each match and replace is an expression whose operators are the names of operator implementations rather than pure mathematical operators. Any variable must be listed in the type information with its associated representation. See the softposit-herbie plugin for a more concrete example.
    (define-ruleset* name (groups ...) #:type ([var type] ...)
    [rule-name match replace]
    ...)
    Like define-ruleset, but it defines a ruleset for every representation that inherits from type. Currently, every type must be the same, e.g. all real, for this procedure to function correctly. Unlike define-ruleset, match and replace contain the names of operators rather than operator implementations.

    Procedures for declaring constants are not a part of the plugin interface. Instead, constants and constant implementations are defined as zero-argument operators and operator implementations. The fields fl-fn, bf-fn, and ival-fn should be implemented with zero-argument procedures (thunks). Similar to operator and operator implementations, constants describe pure mathematical values like π or e while constant implementations define an approximation of those constants in a particular representation.

    Plugin Interface (Parameterized)
    Defining operators, constants, and representations for parameterized functions requires a generator procedure for just-in-time loading of features for a particular representation. When Herbie encounters a representation it does not recognize (not explicitly defined using define-representation) it queries a list of generators in case the representation requires just-in-time loading.

    The following procedure handles represention objects:

    (get-representation name)
    Takes a representation name and returns a representation object. Do not call this function before the associated representation has been registered!

    The following procedures handle generators:

    (register-generator! gen)
    Adds a representation generator procedure to Herbie's set of generators. Representation generator procedures take the name of a representation and return the associated representation object if it successfully created the operators, constants, and rules for that representation. In the case that your plugin does not register the requested representation, the generator procedure need not do anything and should just return false.
    (register-conversion-generator! gen)
    Adds a conversion generator procedure to Herbie's set of generators. Conversion generator procedures take the names of two representations and returns true if it successfully registered conversion(s) between the two representations. Conversions are one-argument operator implementations of the cast operator that have one representation as an input representation and a different representation as an output representation. User-defined conversions are OPTIONAL for multi-precision optimization, since Herbie can synthesize these by default. However Herbie's implementations are often slow since they are representation-agnostic and work for any two representations. In the case that your plugin does not register the requested conversion(s), the generator procedure need not do anything and should just return false.

    To actually add representations, operators, etc. within a generator procedure, you must use a set of alternate procedures.

    (register-representation! name
    type
    repr?
    bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Like define-representation, but used within generators.
    (register-representation-alias! name repr)
    Adds an alias name for an existing representation repr. If two representations are equivalent, e.g. (float 8 32) and binary32, this procedure can be used to declare the two representations equivalent.
    (register-operator! op name itype-names otype-name attribs)
    Like define-operator, but used within generators. The argument itype-names is a list of the input types while the argument attribs are the same attributes for define-operator, e.g. bf. In this case, attribs is an association: (list (cons 'bf bf-fn) ...).
    (register-operator-impl! op name ireprs orepr attribs)
    Like define-operator-impl, but used within generators. Unlike define-operator-impl, this procedure takes representation objects rather than representation names for ireprs and orepr. Use get-representation to produce these objects. See register-operator! for a description of attribs.
    (register-ruleset! name groups var-repr-names rules)
    Like define-ruleset, but used within generators. In this case, groups is a list of rule groups; var-repr-names is an association pairing each variable in the ruleset with its representation, e.g. (list (cons 'x '(float 5 16)) ...); and rules is a list of rules of the following form (list (list rule-name match replace) ...).
    ================================================ FILE: www/doc/2.0/release-notes.html ================================================ Herbie 2.0 Release Notes

    Herbie 2.0 Release Notes

    The Herbie developers are excited to announce Herbie 2.0! This release focuses clarity, transparency, and trade-offs.

    What is Herbie? Herbie automatically improves the accuracy of floating point expressions. This avoids the bugs, errors, and surprises that so often occur with floating point arithmetic. Visit the main page to learn more about Herbie.

    The Herbie 2.0 team at UW and U of U

    Join us! Join the Herbie developers on the FPBench Slack, at the monthly FPBench community meetings, and at FPTalks 2023.

    Speed-accuracy trade-offs

    The Accuracy vs Speed section of a Herbie report, which has an accuracy vs speedup Pareto plot on the left and a tabular view of the accuracy and speed of each of Herbie's alternatives on the right.
    Herbie's new Accuracy vs Speed view, showing how multiple alternatives found by Herbie compare on these different axes.

    Two years ago, Herbie 1.5 added the --pareto mode, wherein suggested multiple alternatives that trade off speed and accuracy. We've made this mode faster, clearer, and more reliable, and as a result, we've made this mode the default. Speed-accuracy trade-offs are central to numerics, and we're excited for Herbie to help users with these trade-offs.

    New Metrics and Redesign

    The metrics shown at the top of each Herbie report: percentage accurate, time, alternatives, and speedup.
    The metrics now being showcased on a Herbie results page: percentage accuracy replaces the old "bits of error" metric, while "speedup" now highlights the speed of the fastest alternative found by Herbie that is also more accurate than the initial program.

    We've cleaned up the report page significantly. Besides a visual redesign—we've refreshed and standardized the new fonts, colors, and spacing—we've switched to a new way of measuring accuracy and speed, which we think will be more intuitive for users and avoid some misconceptions we see users run into.

    Derivations

    The derivation section shown for each alternative produced by Herbie, which show step by step how the initial program was transformed into the alternative.
    New "Step-by-step derivation" buttons show how a transformation was done by Herbie, with each step annotated with its mathematical axiom from Herbie's database.

    Herbie can now explain step by step how it transformed your original input into the final program. This can help you understand how Herbie works, and potentially help us identify bugs or misbehaviors. More importantly, when Herbie comes up with something truly surprising, you can now have more confidence that the answer is correct.

    Foundations for a Herbie Workbench

    We've started building a prototype numerics workbench, codenamed "Odyssey". This release thus contains the beginnings of a REST API for interacting with Herbie. Our long-term goal is an interactive, task-oriented interface for working with floating-point accuracy, leveraging Herbie but also potentially other tools. A prototype is currently available in the VS Code store as the herbie-vscode plugin.

    Other improvements

    • Inputs whose real-valued result rounds to floating-point infinity are now considered valid. This changes Herbie's accuracy estimates, sometimes significantly, but we found that typically this yields better final programs.
    • Most sections of the Herbie reports page now have "help" links directly to the relevant section of the documentation.
    • Many, many algorithms within Herbie, especially pruning, regimes, and binary search, have seen significant speed ups.
    • Graphs are now drawn using JavaScript, on the client side, making them look sharper, especially on high-DPI displays.
    • Herbie's localization pass was written to be more accurate on tricky cases, which improves Herbie's results.
    • Herbie's support for the tgamma and lgamma functions was rewritten to be more accurate (though it remains quite slow).

    Try it out!

    We want Herbie to be 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 or contribute.


    If you find Herbie useful, let us know!

    ================================================ FILE: www/doc/2.0/report.html ================================================ Herbie reports

    Herbie reports

    When used in the browser, Herbie generates HTML reports full of information about the accuracy and relative speed of the initial and alternative expressions.

    The top of the report page has a right-hand menu bar with additional links. “Metrics” give you detailed internal information about Herbie, while “Report”, if present, takes you back to the full Herbie report.

    Below the menu lies a brief summary of the results. Herbie can produce multiple alternatives to the initial program, and this summary gives their basic statistics.

    Summary numbers from a Herbie report.
    Percentage Accurate
    The percentage accuracy of the initial program and what Herbie thinks is its most accurate alternative.
    Time
    The time it took Herbie to generate all of the alternatives.
    Alternatives
    The number of alternatives found.
    Speedup
    The speed of the fastest alternative that is more accurate than the initial program, relative to the initial program.

    Specification

    Next, the specification that you gave to Herbie. This section is closed by default. Typically, the specification is also the initial program, but in some cases, like if the :spec property is used, they can differ. The specification also includes any preconditions given to Herbie.

    You can use the drop-down in the top left to display the specification in an alternative syntax.

    The colored outcome bar summarizes the sampled floating-point inputs that produce valid, unknown, or invalid outputs. Green outcomes are valid, broken down into finite and infinite. Unknown outputs are red. Blue outcomes are invalid (fail precondition or domain errors) and are ignored by Herbie.

    Local Percentage Accuracy graph

    Next, the Local Percentage Accuracy graph compares the accuracy of the initial program to Herbie's most accurate alternative. 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. You can add a precondition to restrict Herbie to the more important inputs in that case.

    In the plot, each individual sampled point is shown with a faint circle, and the thick line is moving average of those individual samples. The red line is the initial program and the blue line is Herbie's most accurate alternative.

    Accuracy is shown on the vertical axis, and higher is better. The horizontal axis shows one of the variables in the input program; the dropdown in the title switches between input variables. The checkboxes below the graph toggle the red and blue lines on and off.

    If Herbie decided to insert an if statement into the program, the locations of those if statements will be marked with vertical bars.

    Accuracy vs Speed

    Next, a Pareto graph and table list the alternatives Herbie found.

    Both the plot and the table show the same data. In the plot, accuracy is on the vertical axis and speedup is on the horizontal axis. Up and to the right is better. The initial program is shown with a red square, while each of Herbie's alternatives is shown with a blue circle. A faint line shows the Pareto frontier—that is, it goes through all Herbie alternatives that maximize speed for their level of accuracy. Some of Herbie's alternatives may not be on the Pareto frontier due to differences between the training and test set.

    In the table, each alternative is shown along with its accuracy and speed relative to the initial program. Values are green if they are better than the initial program, and black otherwise. Each alternative is linked to its description lower on the page.

    Initial program and Alternatives

    Below the table come a series of boxes detailing the initial program and each of Herbie's alternatives, along with their accuracy and relative speed.

    The accuracy and relative speed of each alternative is given in the title. Below the title, the alternative expression itself is given. The dropdown in the top right can be used to change the syntax used.

    By definition, the speed of the initial program is 1.0×, and it has no derivation since it was provided by the user.

    Each alternative also has a derivation, which can be shown by clicking on "Derivation". The derivation shows each step Herbie took to transform the initial program into this alternative.

    Each step in the derivation gives the accuracy after that step. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program. The derivation will also call out any time the input is split into cases. When a part of the step is colored blue, that identifies the changed part of the expression.

    Derivations may also contain "step-by-step derivations"; you can click on those step-by-step derivations to expand them. Each step in the step-by-step derivation names an arithmetic law from Herbie's database, with metadata-eval meaning that Herbie used direct computation in a particular step.

    Reproduction

    Finally, Herbie gives a command to reproduce that result. If you find a Herbie bug, include this code snippet when filing an issue.

    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.

    ================================================ FILE: www/doc/2.0/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/2.0/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 in floating-point. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using it.

    Giving Herbie expressions

    Start Herbie with:

    herbie web

    After a brief wait, your web browser should open and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Let's start by just looking at an example of Herbie running. Click "Show an example". This will pre-fill the expression sqrt(x + 1) - sqrt(x) with x ranging from to 0 and 1.79e308.

    The input range field in the Herbie web UI.

    Now that you have an expression and a range for each variable, click the "Improve with Herbie" button. You should see the entry box gray out, and shortly thereafter some text should appear describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results.

    The very top of this results page gives some quick statistics about the alternative ways Herbie found for evaluating this expression:

    Statistics and error measures for this Herbie run.

    Here, you can see that Herbie's most accurate alternative has an accuracy of 99.7%, much better than the initial program's 53.2%, and that in total Herbie found 5 alternative. One of those alternatives is both more accurate than the original expression and also 1.9× faster. The rest of the result page shows each of these alternatives, including details like how they were derived. These details are all documented, but for the sake of the tutorial let's move on to a more realistic example.

    Programming with Herbie

    You can use Herbie on expressions from source code, mathematical models, or debugging tools. But most users use Herbie as they write code, passing any complex floating-point tool to Herbie as they go. Herbie has options to log all the expressions you enter so that you can refer to them later.

    But to keep the tutorial focused, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need to start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small core that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or prints results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. The bug we're interested in is about complex square root, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, five different number types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total, this code turns into four Herbie inputs: two output fields, for each of the two branches.

    Let's focus on the first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative (but it runs for all values of x.re). So, select the full range of values for x.re, but restrict the range of x.im, like this:

    Restricting the input range to xim >= 0.
    This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression.

    Herbie's results

    Herbie will churn for a few seconds and produce a results page. In this case, Herbie found 4 alternatives, and we're interested in the most accurate one, which should have an accuracy of 84.6%:

    Below these summary statistics, we can see a graph of accuracy versus input value. By default, it shows accuracy versus xim; higher is better:

    The drop in accuracy once xim is bigger than about 1e150 really stands out, but you can also see that Herbie's alternative more accurate for smaller xim values, too. You can also change the graph to plot accuracy versus xre instead:

    This plot makes it clear that Herbie's alternative is almost perfectly accurate for positive xre, but still has some error for negative xre.

    Herbie also found other alternatives, which are less accurate but might be faster. You can see a summary in this table:

    Herbie's algorithm is randomized, so you likely won't see the exact same thing; you might see more or fewer alternatives, and they might be more or less accurate and fast. That said, the most accurate alternative should be pretty similar.

    That alternative itself is shown lower down on the page:

    A couple features of this alternative stand out immediately. First of all, Herbie inserted an if statement. This if statement handles a phenomenon known as cancellation, and is part of why Herbie's alternative is more accurate. Herbie also replaced the square root operation with the hypot function, which computes distances more accurately than a direct square root operation.

    If you want to see more about how Herbie derived this result, you could click on the word "Derivation" to see a detailed, step-by-step explanation of how Herbie did it. For now, though, let's move on to look at another alternative.

    The fifth alternative suggested by Herbie is much less accurate, but it is about twice as fast as the original program:

    This alternative is kind of strange: it has two branches, and each one only uses one of the two variables xre and xim. That explains why it's fast, but it's still more accurate than the initial program because it avoids cancellation and overflow issues that plagued the original.

    Using Herbie's alternatives

    In this case, we were interested in the most accurate possible implementation, so let's try to use Herbie's most accurate alternative.

    // Herbie version of 0.5 * sqrt(2.0 * (sqrt(xre*xre + xim*xim) + xre))
    var r = Math.hypot(x.re, x.im);
    var re;
    if (xre + r <= 0) {
        re = 0.5 * Math.sqrt(2 * (x.im / (x.re / x.im) * -0.5));
    } else {
        re = 0.5 * Math.sqrt(2 * (x.re + r));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the Herbie query in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, including JavaScript, you can use the drop-down in the top-right corner of the alternative block to translate Herbie's output to that language. However, you will still probably need to refactor and modify the results to fit your code structure, just like here.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/2.0/using-cli.html ================================================ Using Herbie from the Command Line

    Shell and Batch Mode

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    herbie shell
    Herbie 2.0 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore
      (x)
      ...
      1)

    Herbie suggests that 1 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell only shows Herbie's most accurate variant.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie on 3 problems (seed: 1551571787)...
      1/3	[   0.882s]   30→ 0	Cancel like terms
    Warning: 24.7% of points produce a very large (infinite) output.
    You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/2.0/faq.html#inf-points> for more.
      2/3	[   1.721s]   29→ 0	Expanding a square
      3/3	[   2.426s]    0→ 0	Commute and associate

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

    ;; seed: 1551571787
    
    (FPCore (x) ... 1)
    (FPCore (x) ... (* x (- x -2)))
    (FPCore (x y z) ... 0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/2.0/using-web.html ================================================ Using Herbie from the Browser

    The Browser UI

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie. Run the Herbie web shell with this command:

    herbie web

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

    herbie web
    Herbie 2.0 with seed 1003430182
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    You can type an input expressions in standard mathematical syntax. After typing in an expression, you will be asked to specify the range of values for each input variable that Herbie should consider when trying to improve the expression. Hit the "Improve with Herbie" button once you're done to run Herbie.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    Batch report generation

    A report can also be generated directly from a file of input expressions in FPCore format:

    herbie report bench/tutorial.fpcore output/ 
    Starting Herbie on 3 problems (seed: 770126425)...
    Warning: 25.0% of points produce a very large (infinite) output. 
    You may want to add a precondition.
    See https://herbie.uwplse.org/doc/latest/faq.html#inf-points for 
    more.
      1/3   [   0.703s]   29→ 0     Expanding a square
      2/3   [   1.611s]    0→ 0     Commute and associate
      3/3   [   0.353s]   30→ 0     Cancel like terms

    This command asks generates a report from the input expressions in bench/tutorial.fpcore and saves the report in the directory output/. It's best if that directory doesn't exist before running this command, because otherwise Herbie may overwrite files in that directory.

    Occasionally you may also see Herbie emit warnings as shown above. All of Herbie's warnings are listed, with explanations, in the FAQ.

    Once generated, open output/index.html in your favorite browser (but see the FAQ if you're using Chrome). That page summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see their report.

    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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/2.1/api-endpoints.html ================================================ Herbie HTTP API Endpoints

    HTTP API

    The Herbie API allows applications to interface with Herbie using HTTP requests. The API is designed to be stateless: the order in which endpoints are called shouldn't matter.

    Format for all endpoints

    All the endpoints listed below respond to POST requests unless otherwise specified. A typical example of sending a POST request to a running Herbie server is:

    curl -d \
      '{"formula": "(FPCore (x) (- (sqrt (+ x 1))))", "seed": 5}' \
      -H 'Content-Type: application/json' \
      http://127.0.0.1:8000/api/sample
        

    /api/sample

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        seed: <random seed for point generation>
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The sample endpoint allows the user to request a sample of points given the FPCore expression and a seed.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/exacts

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        sample: [point, ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The exacts endpoint allows the user to request the exact value of a set of points evaluated at a real number specification given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/calculate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The calculate endpoint allows the user to request the evaluation of a set of points evaluated at a floating-point implementation given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the evaluation of each point using the given FPCore as a floating-point implementation. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the evaluated value; the evaluated value of point n is points[n][1].

    /api/analyze

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      points: [[point, error], ... ]
    }

    The analyze endpoint allows the user to request error analysis of a set of point-exact pairs and a given floating-point implementation.

    Given a collection of points, their exact values, and an FPCore expression to analyze on, the analyze endpoint returns the error for each point for that expression. The error value returned is Herbie's internal error heuristic.

    /api/alternatives

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      alternatives: [alt, ... ],
      histories: [history, ... ],
      splitpoints: [splitpoint, ... ]
    }

    The alternatives endpoint allows the user to request rewrites from Herbie given an expression to rewrite and a set of point-exact pairs.

    Returns a list of alternatives represented by FPCore expressions through the "alternatives" field.

    Returns a list of derivations of each alternative through the "histories" field where history[n] corresponds to alternatives[n].

    Returns a list of splitpoints for each alternative through the "splitpoints" field where splitpoints[n] corresponds to alternatives[n]. splitpoints[n] will only contain information about the corresponding alternative.s splitpoints if the alternative is a branch-expression.

    /api/mathjs

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>
    }

    Response:

    {
      mathjs: <mathjs expression>
    }

    The mathjs endpoint allows the user to translate FPCore expressions into mathjs expressions.

    /api/cost

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        cost: cost
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 13120
    }

    Lower-Cost Example: (x+1)-(x)

    Request:

    {
        formula: <(FPCore (x) (- (+ x 1 ) x))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 320
    }

    The cost endpoint allows the user to request the evaluation of an expression's overall cost. Cost is calculated depending on the complexity of operations contained in the expression.

    Given an FPCore expression and a collection of points, returns the cost of the expression. The cost value returned is calculated internally by Herbie.

    The points should be of the same format as the points generated in the sample endpoint. Refer to /api/sample for more details.

    Note the sample points are not used in the cost calculation. The contents of the points do not matter as long as they are in the correct format as mentioned above.

    /api/translate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        language: "language"
    }

    Response:

    {
        result: "translated expression"
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        language: "python"
    }

    Response:

    {
        result: "def expr(x): return math.sqrt((x + 1.0)) - math.sqrt(x)"
    }

    The translate endpoint allows users to translate FPCore expressions into equivalent expressions in various programming languages.

    Given an FPCore expression and a target language, this endpoint returns the translated expression in the specified language. The language parameter should be a string representing the desired programming language. The response includes the translated expression.

    Currently supported languages are: python, c, fortran, java, julia, matlab, wls, tex, and js.

    ⚠️ Beta endpoints

    The endpoints below are currently a work in progress.

    /api/localerror

    Forthcoming.

    /api/timeline/{job-id}

    Retrieves the timeline data for a given API call. You may find the job id in either the JSON response or in the headers of the HTTP response for of the endpoints in Herbie.

    The timeline is an exception to the others in that it uses a GET request. Below is a sample of what the request might look like. You may consult the /infra/testApi.mjs file for current examples of how to use this API.

    curl -X GET \
      http://127.0.0.1:8000/api/timeline/0b95161a77fc3e29376bbb013d96c2827e2a1cd7
          
    ================================================ FILE: www/doc/2.1/diagrams.html ================================================ Diagrams

    Diagrams

    This page contains systems diagrams for Herbie.

    System diagram of Herbie
    High-level system diagram of Herbie. It highlights Herbie's core architecture, external libraries, and user interactions. Basic flow: Herbie passes user input (expression, precondition, etc.) to a sampler which generates points on which Herbie can evaluate any candidate expression's error. The mainloop (scheduler) then alternates between generate and test phases, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-test phase is complete, Herbie extracts either one or many output expressions using an algorithm called regime inference. Regime inference chooses the "best" (usually most accurate) generated candidate expression or combines multple candidates, each "best" on a smaller part of the input range, with a branch condition.
    ================================================ FILE: www/doc/2.1/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    We recommend most users install Herbie from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker commands with sudo or run them as the administrative user.

    With Docker installed, you can run the Herbie shell with:

    docker run -it uwplse/herbie shell

    This will download the Herbie image and then run its shell tool.

    Herbie in Docker is more limited; for example, it will not recognize plugins installed outside the Docker container.

    Running the web interface

    You can run the Herbie web server locally with

    docker run -it --rm -p 8000:80 uwplse/herbie
    and access the server at http://localhost:8000.

    (Herbie's Docker image binds to port 80 by default; this command uses the -p <hostport>:80 option to expose Herbie on port 8000.)

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    Building the Docker image

    This section is primarily of interest for the Herbie developers.

    Clone the repo and confirm that Herbie builds correctly with make install.

    Next, examine the Dockerfile and Makefile together. The Dockerfile should follow a process exactly like the Makefile, except a clean initial environment is assumed. The build may be split into 2 or more stages to limit the size of the resulting image. Each stage consists of a FROM command and a series of further commands to build up the desired environment, and later stages can refer to earlier stages by name—for example, COPY --from=earlier-stage ... can copy files compiled in earlier images. You may need to do things like bumping the version of Rust used for binary compilation or the version of Racket used in production, or adjusting paths to match the newest version of the repo.

    Once you are ready to build, run this command from the repository root:

    docker build -t uwplse/herbie:test .

    This builds a new test image and tags it uwplse/herbie:test. You can run this image with:

    docker run -p 8000:80 -it uwplse/herbie:test

    The web demo should now be visible at http://localhost:8000.

    To open a shell in a running container for testing, first get the container ID with:

    docker ps

    Then open a root shell in that container with

    docker exec -it <CONTAINER ID> sh

    The code and egg-herbie binaries should be under /src.

    ================================================ FILE: www/doc/2.1/error.html ================================================ What is Error?

    What is Error?

    Herbie helps you identify and correct floating point error in your numeric programs. But what is floating point error, and how does Herbie measure it?

    The summary

    When Herbie reports a "percentage accuracy" number like 92.3%, it's usually best to think of that as the percentage of floating-point inputs where the expression is reasonably accurate.

    The impact of this error on your application will depend a lot on which 7.7% of inputs are inaccurate, and what kind of error that is. You can find this out using the accuracy graph in Herbie reports. You can also use preconditions to restrict the inputs Herbie is considering.

    Why rounding matters

    In mathematics, we work with real numbers, but on a computer, we typically use floating-point numbers. Because there are infinitely many real numbers, but only finitely many floating-point numbers, some real numbers can't be accurately represented. This means that every time you do an operation, the true result typically has to be rounded to a representable one.

    Take an extreme example: the code 1e100 + 1, which increments a huge number in IEEE 754 double-precision floating-point. There's an exact real-number answer, a one followed by 99 zeros and then another 1, but the closest floating-point value is the same as 1e100.

    Errors like this can cause problems. In the example above, the answers differ by one part per googol, which is pretty small. But the error can grow. For example, since 1e100 + 1 rounds to the same value as 1e100, the larger computation

    (1e100 + 1) - 1e100
    returns 0 instead of the correct answer, 1. Now the difference is pretty stark, and can grow even bigger through later operations.

    Bits of error

    There are lots of ways to measure how much rounding error there is in a computation. Most people find the notions of absolute and relative error most intuitive, but Herbie internally uses a more complex notion called bits of error.

    The bits of error metric imagines you have a list of all of the possible floating-point numbers, from largest to smallest. In that list, compare the floating-point value you computed to the one closest to the true answer. If they are the same, that's called 0 bits of error; if they are one apart, that's called one bit of error; three apart is two bits of error; and so on.

    In general, if the two floating-point values are n items apart, Herbie says they have log2(n + 1) bits of error. Values like 0 and -0 are treated as having 0 bits of error, and NaN is considered to have the maximum number of bits of error against non-NaN values. While there's all sorts of theoretical justifications, Herbie mainly uses this error metric because we've found it to give the best results.

    On a single input, the best way to interpret the "bits of error" metric is that it tells you roughly how many bits of the answer, starting at the end, are useless. With zero bits of error, you have the best answer possible. With four bits, that's still pretty good because it's four out of 64. But with 40 or 50 bits of error, you're getting less accuracy out of the computation than even a single-precision floating-point value. And it's even possible to have something like 58 or 60 bits of error, in which case even the sign and exponent bits (which in double-precision floating-point occupy the top 12 bits of the number) are incorrect.

    Percentage accuracy

    Because different number representations have different numbers of bits, Herbie shows the percentage of bits that are accurate instead of the bits of error. With double-precision floats, for example, 75% accurate means 16 bits of error.

    Bits of error measures the error of a computation for some specific input. But usually you're interested in the error of a computation across many possible inputs. Herbie therefore averages the accuracy percentage across many randomly-sampled valid inputs.

    Typically, inputs points are either very accurate or not accurate at all. So when computing percentage accuracy, Herbie's averaging a lot of points with near-100% accuracy and a lot of points with near-0% accuracy. In that sense, you can think of percentage accuracy as measuring mostly what percentage of inputs are accurate. If Herbie says your computation is 75% accurate what it's really saying is that about a quarter of inputs produce usable outputs.

    Valid inputs

    Since percentage accuracy averages over many points, it depends on what points it is averaging over, and how is samples among them. Herbie samples among valid inputs, uniformly distributed.

    Herbie considers an input valid if it is a floating-point value in the appropriate precision and its true, real-number output 1) exists; 2) satisfies the user's precondition; and 3) can be computed. Let's dive into each requirement.

    1. An output can fail to exist for an input if that input does something like divide by zero or take the square root of a negative number. Then that input is invalid.
    2. An input can fail to satisfy the user's precondition. Preconditions can be basically anything, but most often they specify a range of inputs. For example, if the precondition is (< 1 x 2), then the input x = 3 is invalid.
    3. Finally, and most rarely, Herbie can fail to compute the output for a particular input. For example, the computation (/ (exp 1e9) (exp 1e9)), which divides two identical but gargantuan numbers, does have an exact real-number answer (one), but Herbie can't compute that exact answer because the intermediate steps are too large. This input is also invalid, but you'll get a warning if this happens.

    Herbie's percentage accuracy metric only averages over valid points. This means that when you change your precondition, you change which points are valid, and that can change the percentage accuracy reported by Herbie. This is useful: if you know there's a rare error, but Herbie thinks error is low, you can change your precondition to focus on the points of interest.

    Some inputs are valid in surprising ways. For example, consider the expression (exp x) for x = 1e100. The true real-number result is a very large but finite number; but that real number, whatever it is, rounds to infinity in double-precision floating point. Herbie does consider this input valid, but infinite values are a little odd in floating-point so you might find this surprising. For this reason, Herbie issues a warning if too many outputs are infinite.

    Sampling inputs

    When randomly sampling inputs, Herbie considers each valid input equally likely. Importantly, this does not mean that it uses a uniform distribution, because floating-point values themselves are not uniformly distributed.

    For example, there are as many floating-point values between 1 and 2 as there are between one and one half, because floating-point values use an exponential encoding. But that means that if you're looking at a computation where the input comes from the interval [0.5, 2], Herbie will weigh the first third of that range twice as high as the other two thirds.

    This can produce unintuitive results, especially for intervals that cross 0. For example, in the interval [0, 1], the second half of that interval (from one half to one) has a tiny proportion of the weight (in double-precision floating-point, about 0.1%). If Herbie can improve accuracy by a little bit between zero and one half, while dramatically reducing accuracy between one half and one, it will think that that's an accuracy improvement. You might disagree.

    Unfortunately, there's no way for Herbie to intuit exactly what you mean, so understanding this error distribution is essential to understanding Herbie's outputs. For example, if Herbie reports that accuracy improved from 75% to 76%, it's essential to know: is the improvement happening on inputs between one half and one, or between 1e-100 and 2e-100? To answer this question, it's important to look over the reports and graphs generated by Herbie.

    ================================================ FILE: www/doc/2.1/faq.html ================================================ Herbie FAQ

    Common Errors and Warnings

    Herbie automatically transforms floating point expressions into more accurate forms. This page troubleshoots common Herbie errors, warnings, and known issues.

    Common errors

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

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized. (- (exp x) 1) would be the correct way to write that expression. The input format documentation has more details on Herbie's syntax.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. The simplest fix is to increase the --num-analysis flag. Specifying the range of valid points as a precondition can also help.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. The solution is to fix the precondition or input program.

    Common warnings

    Herbie warnings refer to this section for explanations and common actions to take.

    Infinite outputs for N% of points.

    Sometimes, an input to your expression produces an output so large that it's best represented by a floating-point infinity. For example, (exp 1000) is over 10434, so it's much larger than the largest floating-point value. Herbie raises this warning when too many inputs (more than 20% of them) are this large, because that usually indicates you should set a more restrictive precondition.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10,000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for very large x, absurdly large numbers would be required. Herbie discards such inputs and raises this warning. If you see this warning, you should add a restrictive precondition, such as :pre (< -100 x 100), to prevent large inputs.

    Could not uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a string representation for a given value. This is likely the result of a bug in a third-party plugin.

    Unsound rule application detected

    Herbie uses a set of algebraic rewrite rules in order to simplify expressions, but these rules can sometimes lead to a contradiction. Herbie will automatically compensate for this, and in most cases nothing needs to be done. However, Herbie may have failed to simplify the output.

    Unused variable var

    The input FPCore contains a variable that is not used in the body expression.

    Strange variable var

    The input expression contains a variable that is similar in name to named constants, e.g. e instead of E.

    Known bugs

    Bugs that cannot be directly fixed are documented in this section.

    Missing reports chart on Chrome

    When using Chrome to view web pages on your local machine, Herbie reports cannot draw the arrow chart due to security restrictions. Run Chrome with --allow-file-access-from-files to fix this error.

    ================================================ FILE: www/doc/2.1/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    Math format

    The Herbie web shell takes input in a subset of the math.js syntax. The web shell automatically checks for syntax errors, and provides a graphical user interface for specifying the input domain. The web shell converts the mathematical expression and input ranges into FPCore before sending it to Herbie.

    FPCore format

    Herbie's command-line and batch-mode tools use FPCore format to describe mathematical expressions. FPCore looks like this:

    (FPCore (inputs ...) properties ... expression)
    (FPCore name (inputs ...) properties ... expression)

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context. If name is specified, the function can be referenced in subsequent FPCore declarations in the file.

    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))))

    The semicolon (;) character introduces a line comment. 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 include:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. Use - for both subtraction and negation.

    Herbie links against your computer's libm to evaluate these functions. So, each function has the same behavior in Herbie as in your code. You can instead use the racket precision if you instead want reproducible behavior.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison functions support chained comparisons with more than two arguments.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    Unless you have a lot of Lisp experience, you'll probably find let* more intuitive.

    Internally, Herbie treats intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy. Using intermediate variables will therefore not produce a more accurate result or help Herbie run faster.

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will only use the :spec expression to evaluate error, not to search for accurate expressions.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point
    racket
    Like binary64, but using Racket math functions rather than your computer's libm.

    By default, binary64 is assumed. Herbie also has a plugin system to load additional precisions.

    Herbie won't produce mixed-precision code unless you allow it to do so, using the :herbie-conversions property:

    :herbie-conversions
    ([prec1 prec2] ...)
    Expressions in precision prec1, can be rewriten to use precision prec2, and vice versa.

    All conversions are assumed to be bidirectional. For example, if you specify :herbie-conversions ([binary64 binary32]), Herbie will be able to add conversions between single- and double-precision floating-point.

    Miscellaneous Input Properties

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie allows :alt properties to specify additional "developer targets"; these might be other alternatives you've tried that you want to compare against.

    Additional Output Metadata

    Herbie's output provides additional information in custom properties:

    :herbie-status status
    status describes whether Herbie worked: it 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-error-input
    ([pts err] ...)
    The average error of the input program at pts points. Multiple entries correspond to Herbie's training and test sets.
    :herbie-error-output
    ([pts err] ...)
    The computed average error of the output program, similar to :herbie-error-input.

    Herbie's benchmark suite also uses properties for continuous integration, but these are not officially supported and their use is discouraged.

    ================================================ FILE: www/doc/2.1/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. To start, install Racket, which Herbie is written in. Then install Herbie, either from a package, from source, or via a Docker image.

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.0 are supported, but more recent versions are faster. We strongly recommend v8.7 or newer.

    On Linux, we recommend against installing Racket via Snap. If you must install Racket from Snap, locate Herbie and all other packages in your home directory or another allow-listed directory.

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

    racket
    Welcome to Racket v8.13 [cs].
    > (exit)

    Installing Herbie from a package

    Herbie can be installed from a binary package on Windows and Linux for x86-64, and on macOS on either x86-64 or ARM. If you are on a more obscure platform, please install from source instead.

    First install Racket. Then install Herbie from a package with:

    raco pkg install --auto herbie

    Then check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.1

    If you'd like, you can run Herbie with the herbie command by adding the following directory to your PATH (example paths for Racket 8.13):

    • On Windows, AppData\Roaming\Racket\8.13\bin in your user folder.
    • On macOS, Library/Racket/8.13/bin in your home folder.
    • On Linux, .local/share/racket/8.13/bin in your home directory.

    Please note that the path of the binary will be different on Linux if you have opted (against recommendation) to use Snap. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie from source

    Installing Herbie from source is best if you plan to develop Herbie or Herbie plugins, or if you use a more obscure hardware/OS combination. The instructions assume a standard Unix userland; on Windows, you may have to install tools like Make separately, or use WSL.

    Install Racket. Then install Rust, using rustup or via some other means. Versions as old as 1.60.0 are supported.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

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

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other directories. Install Herbie with:

    make install

    This command installs Herbie and its dependencies and compiles it for faster startup. Check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.1

    You can also follow the install-from-package instructions to add herbie to your path, if you'd like.' Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like a virtual machine. Herbie in Docker is more limited; we do not recommend using Herbie through Docker without prior Docker experience.

    The Docker documentation describes how to install and run the official uwplse/herbie image.

    ================================================ FILE: www/doc/2.1/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    Herbie commands

    There are a few different methods for interacting with Herbie, which we call tools. For example, Herbie can be run both interactively or in batch mode, and can generate output intended for the command line or for the web. Herbie provides four of these tools as subcommands:

    racket -l herbie web
    Use Herbie through your browser. The web command runs Herbie on your local machine and opens its main page in your browser.
    racket -l herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie will print its more-accurate version.
    racket -l herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    racket -l herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. Viewing these requires a web server.

    We recommend web which produces reports with lots of information about floating-point accuracy, including graphs of error versus input values and plots comparing cost and accuracy. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie tool --help to view available command-line options for a tool. This command also shows undocumented subcommands 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, like this:

    racket -l herbie report --timeout 60 in.fpcore out/

    Arguments cannot go before options.

    --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 (not including the latter). This option can be used to make Herbie's results reproducible or to compare two different runs. If not specified, a random seed is chosen.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of times Herbie attempts to improve accuracy. The default, 4, suffices for most programs and helps keep Herbie fast; in practice iterations beyond the first few rarely lead to lower error. Increase this option, say to 6, to check that there aren't further improvements that Herbie could seek out.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 12. Increasing this option will slow Herbie down, but may fix the "Cannot sample enough valid points" error.
    --num-enodes N
    The number of equivalence graph nodes to use when doing algebraic reasoning. The default is 8000. Increasing this option will slow Herbie down, but may improve results slightly.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given.
    --threads N (for the improve and report tools)
    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 of the hardware threads.
    --no-pareto
    Disables cost-aware search. Herbie will only return a single program, which optimizes exclusively for accuracy. Herbie will be slightly faster as a result.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --no-browser
    This flag disables the default behavior of opening the Herbie page in your default browser.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.) Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate programs.

    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
    boolsBoolean operator identities
    branchesif statement simplification
    specialThe gamma and error functions
    numericsNumerical shorthands expm1, log1p, fma, and hypot

    Search options

    These options change the types of transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    setup:search
    This option, on by default, uses interval subdivision search to help compute ground truth for complicated expressions. If turned off, Herbie will be slightly faster, but may hit the "Cannot sample enough valid points" error more often. Instead of turning this option off, you may want to adjust the --num-analysis flag instead.
    setup:simplify
    This option, on by default, simplifies the expression before starting the Herbie improvement loop. If turned off, Herbie's results will likely be somewhat worse, but its output program may be more similar to the input program.
    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 Herbie's creativity.
    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. If turned off, Herbie's results will more likely be algebraically equivalent to the input expression, but may be less accurate.
    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.
    generate:proofs
    This option, on by default, generates step-by-step derivations for HTML reports. If turned off, the step-by-step derivations will be absent, and Herbie will be slightly faster.
    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). Turn this option off if your programming environment makes branches very expensive, such as on a GPU.
    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. 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 inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.
    ================================================ FILE: www/doc/2.1/plugins.html ================================================ Herbie Plugins

    Plugins

    Herbie plugins define new functions, add rewrite rules, and number representations. Users install plugins separately, and Herbie then automatically loads and uses them.

    Posit arithmetic

    The softposit-herbie plugin implements support for posit arithmetic. Install it with:

    raco pkg install --auto softposit-herbie

    This plugin uses the SoftPosit library, only supported on Linux. Even then is reported to misbehave on some machines. The plugin supports arithmetic operations, sqrt, and quires.

    Once softposit-herbie is installed, specify :precision posit16 to inform Herbie that it should assume the core's inputs and outputs are posit numbers. Other posit sizes (with 8 or 32 bits) and also quires (for 8, 16, and 32 bit posits) are available, but are poorly supported.

    Generic floating-point numbers

    The float-herbie plugin implements support for any IEEE-754 binary floating-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once float-herbie is installed, specify :precision (float ex nb) to inform Herbie that it should assume the core's inputs and outputs are floating-point numbers with ex exponent bits and nb total bits (sign bit + mantissa bits + exponent bits).

    Generic fixed-point numbers

    The fixedpoint-herbie plugin implements support for any fixed-point number. To install, check out the source code and run

    raco pkg install

    at the top-level directory of the repository. Once fixedpoint-herbie is installed, specify :precision (fixed nb sc) to inform Herbie that it should assume the core's inputs and outputs are signed fixed-point numbers with nb total bits and a scaling factor of 2sc (integer formats have a scaling factor of 20). This plugin also supports unsigned fixed-point numbers specified by :precision (ufixed nb sc) and provides simpler aliases for integer formats with :precision (integer nb) and :precision (uinteger nb).

    Developing plugins

    The following is a guide to creating a Herbie plugin. Plugins are considered experimental and may change considerably between releases. If you run into issues, please file a bug. Be sure to check out the built-in plugins in the Herbie repository before getting started.

    First Steps
    All plugins are implemented as Racket packages. The easiest way to initialize a new Racket package is to run

    raco pkg new pkg-name
    in a new folder. Make sure the folder name is the same as the package name! This will initialize a Racket package with all the necessary files. Read the official Racket documentation on the raco tool for more information.

    A single entry needs to be added to the package manifest stored in info.rkt: add (define herbie-plugin 'name) to the bottom of the file where name is a unique symbol that doesn't conflict with other Herbie plugins, like the package name.

    Next, edit the main.rkt file by erasing everything except the language specifier on the first line, and add the line (require herbie/plugin). This gives the package access to the Herbie plugin interface. Optionally add the following for debugging purposes (eprintf "Loading pkg-name support...\n") directly after the require statement.

    Finally, run the following in the folder containing info.rkt and main.rkt:

    raco pkg install
    This should install your package and check it for errors. Everything is now set up. If you added the debugging line in main.rkt, you should see the string when you run Herbie. Of course, your plugin is empty and doesn't yet add any useful features.

    Adding Features
    Now that you have an empty plugin, you can begin adding new functions, rewrite rules, and number representatons. The procedures exported by the Herbie plugin interface can be roughly divided into two categories: unique and parameterized. Whether or not you use the unique or parameterized half of the interface (or maybe both!) depends entirely on the number representation a feature is being implemented for. First, identify if your number representation is unique or parameterized. For example, if you are adding features for double precision (or rather binary64), the representation is unique. If you are adding features for a generic floating point format, say (float ebits nbits), then the representation is parameterized.

    Plugin Interface (Unique)
    The following are the signatures and descriptions of the plugin procedures for unique representations. These procedures are required to be at the top-level of main.rkt rather than inside a function.

    (define-type name (exact? inexact?) exact->inexact inexact->exact)
    Adds a new type with the unique identifier name. The arguments exact? and inexact? return true if a value is an exact or high-precision approximate representation. For Herbie's real type, exact? is implemented with real? and inexact? is implemented with bigfloat?. The procedures exact->inexact and inexact->exact convert between exact? and inexact? values.
    (define-representation (name type repr?) bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Adds a new representation with the unique identifier name. The representation will inherit all rewrite rules defined for type. By default, Herbie defines two types: real and bool. Your representation will most likely inherit from real. The width argument should be the bitwidth of the representation, e.g. 64 for binary64. The argument repr? is a procedure that accepts any argument and returns true if the argument is a value in the representation, e.g. an integer representation should use Racket's integer?, while special? takes a value in the representation and returns true if it is not finite, e.g. NaN or infinity.

    The other four arguments are single-argument procedures that implement different conversions. The first two convert between a value in your representation and a Racket bigfloat (you need to import math/bigfloat). The last two convert between a value in your representation and its corresponding ordinal value. Ordinal values for any representation must be within the interval [0, 2width - 1]. Check Racket's definition of ordinals for floats. Note that those ordinal values can be negative.
    (define-operator (name itype-names ...) otype-name
    [bf bf-fn]
    [ival ival-fn])
    Adds a new operator. Operators describe pure mathematical functions, i.e. + or sin. The parameters itype-names and otype-name are the input type(s) and output type names. For example, + takes two real inputs and produces one real output. The bf-fn argument is the bigfloat implementation of your operator. The ival-fn argument is the Rival implementation of your operator. This is optional but improves the quality of Herbie's output. If you don't want to implement this, set ival-fn to false. To define operators with an unknown number of arguments, e.g. comparators, add the attribute [itype itype]. This will override the input type names defined by itype-names. See the bottom of this section for support for constants.
    (define-operator-impl (op name irepr-names ...)orepr-name
    [fl fl-fn]
    ...)
    Implements op with input representation(s) irepr-names and output representation orepr-name. The field name must be unique. For example, Herbie implements +.f64 and +.f32 for double- and single-precision floats. The argument fl-fn is the actual procedure that does the computation. Like define-operator, the input representations can be overridden with [itype irepr]. By default, the attributes bf and ival are inherited from op but can be overridden as previously described. See the bottom of this section for support for constant implementations.
    (define-ruleset name (groups ...) #:type ([var repr] ...)
    [rule-name match replace]
    ...)
    Defines a set of rewrite rules. The name of the ruleset as well as each rule-name must be a unique symbol. Each ruleset must be marked with a set of groups (read here on ruleset groups). Each rewrite rule takes the form match ⇝ replace where Herbie's rewriter will replace match with replace (not vice-versa). Each match and replace is an expression whose operators are the names of operator implementations rather than pure mathematical operators. Any variable must be listed in the type information with its associated representation. See the softposit-herbie plugin for a more concrete example.
    (define-ruleset* name (groups ...) #:type ([var type] ...)
    [rule-name match replace]
    ...)
    Like define-ruleset, but it defines a ruleset for every representation that inherits from type. Currently, every type must be the same, e.g. all real, for this procedure to function correctly. Unlike define-ruleset, match and replace contain the names of operators rather than operator implementations.

    Procedures for declaring constants are not a part of the plugin interface. Instead, constants and constant implementations are defined as zero-argument operators and operator implementations. The fields fl-fn, bf-fn, and ival-fn should be implemented with zero-argument procedures (thunks). Similar to operator and operator implementations, constants describe pure mathematical values like π or e while constant implementations define an approximation of those constants in a particular representation.

    Plugin Interface (Parameterized)
    Defining operators, constants, and representations for parameterized functions requires a generator procedure for just-in-time loading of features for a particular representation. When Herbie encounters a representation it does not recognize (not explicitly defined using define-representation) it queries a list of generators in case the representation requires just-in-time loading.

    The following procedure handles represention objects:

    (get-representation name)
    Takes a representation name and returns a representation object. Do not call this function before the associated representation has been registered!

    The following procedures handle generators:

    (register-generator! gen)
    Adds a representation generator procedure to Herbie's set of generators. Representation generator procedures take the name of a representation and return the associated representation object if it successfully created the operators, constants, and rules for that representation. In the case that your plugin does not register the requested representation, the generator procedure need not do anything and should just return false.
    (register-conversion-generator! gen)
    Adds a conversion generator procedure to Herbie's set of generators. Conversion generator procedures take the names of two representations and returns true if it successfully registered conversion(s) between the two representations. Conversions are one-argument operator implementations of the cast operator that have one representation as an input representation and a different representation as an output representation. User-defined conversions are OPTIONAL for multi-precision optimization, since Herbie can synthesize these by default. However Herbie's implementations are often slow since they are representation-agnostic and work for any two representations. In the case that your plugin does not register the requested conversion(s), the generator procedure need not do anything and should just return false.

    To actually add representations, operators, etc. within a generator procedure, you must use a set of alternate procedures.

    (register-representation! name
    type
    repr?
    bigfloat->repr
    repr->bigfloat
    ordinal->repr
    repr->ordinal
    width
    special?)
    Like define-representation, but used within generators.
    (register-representation-alias! name repr)
    Adds an alias name for an existing representation repr. If two representations are equivalent, e.g. (float 8 32) and binary32, this procedure can be used to declare the two representations equivalent.
    (register-operator! op name itype-names otype-name attribs)
    Like define-operator, but used within generators. The argument itype-names is a list of the input types while the argument attribs are the same attributes for define-operator, e.g. bf. In this case, attribs is an association: (list (cons 'bf bf-fn) ...).
    (register-operator-impl! op name ireprs orepr attribs)
    Like define-operator-impl, but used within generators. Unlike define-operator-impl, this procedure takes representation objects rather than representation names for ireprs and orepr. Use get-representation to produce these objects. See register-operator! for a description of attribs.
    (register-ruleset! name groups var-repr-names rules)
    Like define-ruleset, but used within generators. In this case, groups is a list of rule groups; var-repr-names is an association pairing each variable in the ruleset with its representation, e.g. (list (cons 'x '(float 5 16)) ...); and rules is a list of rules of the following form (list (list rule-name match replace) ...).
    ================================================ FILE: www/doc/2.1/release-notes.html ================================================ Herbie 2.1 Release Notes

    Herbie 2.1 Release Notes

    The Herbie developers are excited to announce Herbie 2.1! This release focuses performance, both in the generated code and in the Herbie kernel itself.

    What is Herbie? Herbie automatically improves the accuracy of floating point expressions. This avoids the bugs, errors, and surprises that so often occur with floating point arithmetic. Visit the main page to learn more about Herbie.

    OOPSLA, ASPLOS, and POPL Reviewers, please do not read further, because some of the work described below is submitted for publication to these venues.

    The Herbie 2.1 team at UW and U of U

    Faster Generated Code

    A comparison of speed-accuracy curves for Herbie 2.0 and
         2.1, showing much faster low-accuracy code in Herbie 2.1.
    Herbie 2.0 (green) and 2.1 (blue) speed-accuracy curves on the Hamming test suite, showing that Herbie 2.1 generates much faster code, especially at low accuracy levels. The impact is due to both typed extraction (described on the plot as egg-serialize) and the cost opportunity heuristic (described on the plot as cost localization). The orange curve shows that both components are necessary to achieve the best results.

    Last year, Herbie 2.0 released pareto mode, in which Herbie generates multiple expressions with different speeds and accuracies. Herbie 2.1 now makes Herbie's generated code, especially at the highest-performance/lowest-accuracy level, dramatically better. Some features that contribute to these improvements:

    • With type-based extraction (#875, #883, and #887) Herbie considers performance optimizations like precision tuning at the same time as it considers rewrites, instead of considering each separately.
    • A new cost-opportunity heuristic (#746) allows Herbie to focus on parts of the program that can be sped up, generating faster low-accuracy code.
    • Preprocessing for odd functions (#645) generates range reductions for odd functions, which can mean more accurate generated code.
    • Polynomials are now evaluated in Horner form (#727). Together with some bug fixes (#660), this means faster and more accurate polynomial approximations.

    While Herbie's generated results are much better, these changes alone would make Herbie more than twice as slow. This leads to the second category of changes.

    Faster Herbie Kernel

    Iteration 0Iteration 1Iteration 2
    OperationPrecisionTime (µs)PrecisionTime (µs)PrecisionTime (µs)
    Tuning22.921.0
    cos7875.959298.91695173.1
    add8311.0210710.0269811.0
    cos788.159399.11695176.0
    sub7310.07310.07311.0
    Total105.0241.0392.1
    A precision-tuned execution of cos(x) - cos(x + ɛ) when x = 10300 and ɛ = 10-300. Each row of the table represents one mathematical operation (or the time spent precision-tuning), and each pair of columns describes one iteration precision and execution time for that operation. Each operation's precision is chosen independently, so the precision column is not uniform.

    Nearly every part of Herbie has been sped up, often significantly, meaning that Herbie 2.1 overall—despite the much faster generated code—is only 20% or so slower than Herbie 2.0.

    The most challenging improvement is a complete rewrite of Herbie's real evaluation system, Rival. Herbie 2.1 uses precision tuning to reduce the time and memory costs by approximately 40%, with the biggest impacts to the largest and slowest expressions. Moreover, Rival has been packaged for use in other projects.

    Other optimizations to Herbie include:

    • Regimes saw a series of improvements, including to data layout (#696), sharing (#706), types (#748), and algorithms (#772), which together lead to a 2–3× speed up to regimes.
    • The floating-point program interpreter was rewritten and sped up (#766).
    • Batching sped up derivation generation significantly (#736.)
    • An accidentally-quadratic lookup in pruning was found and fixed (#781).
    • Analysis capped an exponential blow-up that occurred for some preconditions (#762).
    • Random number generation saw a small speed up (#792).

    New Features: Platforms and Explanations

    (define-accelerator (sind real) real
      (λ (x) (sin (* x (/ (PI) 180)))))
    (define-accelerator (cosd real) real
      (λ (x) (cos (* x (/ (PI) 180)))))
    (define-accelerator (tand real) real
      (λ (x) (tan (* x (/ (PI) 180)))))
    A snippet from the Herbie "platform" for the Julia language, describing special library functions cosd, sind, and tand, which Julia provides. When using this platform, Herbie will use these functions in its generated code.

    Two new features are in development and available in an undocumented alpha state in this release: platforms and explanations.

    Platforms allow Herbie to generate code specific to a given programming language, library, or hardware platform. Herbie can use platform-specific operators, cost models, and compilation styles, which leads to faster and more accurate code. We hope to clean up the platforms code and release it for real in Herbie 2.2.

    Explanations describe what floating-point errors Herbie found and what inputs they occur for. This should make Herbie easier to understand and a more valuable tool for learning about floating-point error.

    Sister Projects

    The Odyssey numerics workbench is releasing version 1.1 today, featuring FPTaylor support and expression export. Odyssey and supporting tools like Herbie and FPTaylor can be installed and run locally through the Odyssey VSCode extension. New features include:

    • Support for using FPTaylor to compute sound error bounds in Odyssey. Select "FPTaylor Analysis" from the tool dropdown for an expression. This is a part of a larger effort to combine different floating point tools as parts of an analysis.
    • Odyssey now supports exporting expressions to different languages using the new "Expression Export" tool.
    • Herbie has been updated with an HTTP API endpoint to support Odyssey's expression export. Herbie's HTTP API endpoints are documented here.
    • Like the Herbie demo, Odyssey now shows the percent accuracy of expressions, rather than bits of error.
    • The layout of the Odyssey interface has been updated and will continue to see rolling updates.

    The Rival real-arithmetic package is releasing version 2.0 today, featuring the correct-rounding code from Herbie (#804), including the new precision tuning algorithm and a newly-build profiling system.

    Development Improvements

    • Herbie now supports the FPCore :alt field, including multiple alternative expressions (#764, #783, and #805).
    • Many of Herbie's oldest benchmarks have gained new preconditions and human-written target programs (#693, #697), which will drive Herbie development in the future.
    • Herbie's report page has been totally rewritten, and now uses JavaScript. This has allowed us to sorting, filtration, and diffing capabilities, which has really made development easier. (#641, #651, #687). However, this does mean that you need to run a local server to view report pages saved on your local disk—this is a browser security policy that we can't avoid. You'll see an error message explaning how (#863).
    • Friends at Intel contributed benchmarks from the DirectX specification (#656).
    • Caching has sped up Herbie's continuous integration (#663).

    Other improvements

    • We now build and publish macOS Arm64 packages (#787, #862). Thank you to Github for hosting our build infrastructure.
    • We fixed a memory leak (#636) and segfault (#665) in Herbie's supporting Rust libraries. Eventually the egg folks tracked down the root cause, so this won't be a problem any more.
    • Some sources of non-determinism were tracked down and fixed (#661).
    • Herbie's internals now distinguish between real and floating-point expressions (#676, #702, #732), which has previously been an ad-hoc distinction.
    • Herbie's API endpoints now use an internal job astraction (#845), which should eventually allow them to be threaded and asynchronous.

    Try it out!

    We want Herbie to be 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 or contribute.


    If you find Herbie useful, let us know!

    ================================================ FILE: www/doc/2.1/report.html ================================================ Herbie reports

    Herbie reports

    When used in the browser, Herbie generates HTML reports full of information about the accuracy and relative speed of the initial and alternative expressions.

    The top of the report page has a right-hand menu bar with additional links. “Metrics” give you detailed internal information about Herbie, while “Report”, if present, takes you back to the full Herbie report.

    Below the menu lies a brief summary of the results. Herbie can produce multiple alternatives to the initial program, and this summary gives their basic statistics.

    Summary numbers from a Herbie report.
    Percentage Accurate
    The percentage accuracy of the initial program and what Herbie thinks is its most accurate alternative.
    Time
    The time it took Herbie to generate all of the alternatives.
    Alternatives
    The number of alternatives found.
    Speedup
    The speed, relative to the initial program, of the fastest alternative that improves accuracy.

    Specification

    Next, the specification that you gave to Herbie. This section is closed by default. Typically, the specification is also the initial program, but in some cases, like if the :spec property is used, they can differ. The specification also includes any preconditions given to Herbie.

    You can use the drop-down in the top left to display the specification in an alternative syntax.

    The colored outcome bar summarizes the sampled floating-point inputs that produce valid, unknown, or invalid outputs. Green outcomes are valid, broken down into finite and infinite. Unknown outputs are red. Blue outcomes are invalid (fail precondition or domain errors) and are ignored by Herbie.

    Local Percentage Accuracy graph

    Next, the Local Percentage Accuracy graph compares the accuracy of the initial program to Herbie's most accurate alternative. 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. You can add a precondition to restrict Herbie to the more important inputs in that case.

    In the plot, each individual sampled point is shown with a faint circle, and the thick line is moving average of those individual samples. The red line is the initial program and the blue line is Herbie's most accurate alternative.

    Accuracy is shown on the vertical axis, and higher is better. The horizontal axis shows one of the variables in the input program; the dropdown in the title switches between input variables. The checkboxes below the graph toggle the red and blue lines on and off.

    If Herbie decided to insert an if statement into the program, the locations of those if statements will be marked with vertical bars.

    Accuracy vs Speed

    Next, a Pareto graph and table list the alternatives Herbie found.

    Both the plot and the table show the same data. In the plot, accuracy is on the vertical axis and speedup is on the horizontal axis. Up and to the right is better. The initial program is shown with a red square, while each of Herbie's alternatives is shown with a blue circle. A faint line shows the Pareto frontier—that is, it goes through all Herbie alternatives that maximize speed for their level of accuracy. Some of Herbie's alternatives may not be on the Pareto frontier due to differences between the training and test set.

    In the table, each alternative is shown along with its accuracy and speed relative to the initial program. Values are green if they are better than the initial program, and black otherwise. Each alternative is linked to its description lower on the page.

    Initial program and Alternatives

    Below the table come a series of boxes detailing the initial program and each of Herbie's alternatives, along with their accuracy and relative speed.

    The accuracy and relative speed of each alternative is given in the title. Below the title, the alternative expression itself is given. The dropdown in the top right can be used to change the syntax used.

    By definition, the speed of the initial program is 1.0×, and it has no derivation since it was provided by the user.

    Each alternative also has a derivation, which can be shown by clicking on "Derivation". The derivation shows each step Herbie took to transform the initial program into this alternative.

    Each step in the derivation gives the accuracy after that step. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program. The derivation will also call out any time the input is split into cases. When a part of the step is colored blue, that identifies the changed part of the expression.

    Derivations may also contain "step-by-step derivations"; you can click on those step-by-step derivations to expand them. Each step in the step-by-step derivation names an arithmetic law from Herbie's database, with metadata-eval meaning that Herbie used direct computation in a particular step.

    Reproduction

    Finally, Herbie gives a command to reproduce that result. If you find a Herbie bug, include this code snippet when filing an issue.

    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.

    ================================================ FILE: www/doc/2.1/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/2.1/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating-point expressions to make them more accurate. Floating-point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 in floating-point. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using it.

    Giving Herbie expressions

    Start Herbie with:

    racket -l herbie web

    Alternatively, if you added herbie to the path, you can always replace racket -l herbie with just herbie.

    After a brief wait, your web browser should open and show you Herbie's main window. Re-check the installation steps if this doesn't occur.

    The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Let's start by just looking at an example of Herbie running. Click "Show an example". This will pre-fill the expression sqrt(x + 1) - sqrt(x) with x ranging from 0 to 1.79e308.

    The input range field in the Herbie web UI.

    Now that you have an expression and a range for each variable, click the "Improve with Herbie" button. You should see the entry box gray out, and shortly thereafter some text should appear describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results.

    The very top of this results page gives some quick statistics about the alternative ways Herbie found for evaluating this expression:

    Statistics and error measures for this Herbie run.

    Note that Herbie's algorithm is randomized, so you likely won't see the exact same thing; you might see more or fewer alternatives, and they might be more or less accurate and fast.

    Here, you can see that Herbie's most accurate alternative has an accuracy of 99.7%, much better than the initial program's 53.2%, and that in total Herbie found 5 alternatives. One of those alternatives is both more accurate than the original expression and also 1.9× faster. The rest of the result page shows each of these alternatives, including details like how they were derived. These details are all documented, but for the sake of the tutorial let's move on to a more realistic example.

    Herbie measures accuracy by comparing a program's result against the exact answer calculated using high-precision arithmetic. The difference between these two is then measured in "bits of error" which counts how many of the most significant bits that the approximate and exact result agree on. This error is then averaged across many different sample inputs to determine the program's overall accuracy. Herbie's error documentation describes the process in more detail.

    Programming with Herbie

    You can use Herbie on expressions from source code, mathematical models, or debugging tools. But most users use Herbie as they write code, asking it about any complex floating-point expression they write. Herbie has options to log all the expressions you enter so that you can refer to them later.

    But to keep the tutorial focused, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need to start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small kernel that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or prints results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. We're interested in the complex square root function, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, different types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total, this code turns into four Herbie inputs: two output fields, for each of the two branches.

    Let's focus on the first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative (but it runs for all values of x.re). So, select the full range of values for x.re, but restrict the range of x.im, like this:

    Restricting the input range to xim >= 0.
    This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression. The number 1.79e308 is approximately the largest double-precision number, and will auto-complete.

    Herbie's results

    Herbie will churn for a few seconds and produce a results page. In this case, Herbie found 6 alternatives, and we're interested in the most accurate one, which should have an accuracy of 84.6%:

    Below these summary statistics, we can see a graph of accuracy versus input value. By default, it shows accuracy versus xim; higher is better:

    There's a really obvious drop in accuracy once xim gets bigger than about 1e150 (due to floating-point overflow), but you can also see that Herbie's alternative is more accurate for smaller xim values, too. You can also change the graph to plot accuracy versus xre instead:

    This plot makes it clear that Herbie's alternative is almost perfectly accurate for positive xre, but still has some error for negative xre.

    Herbie also found other alternatives, which are less accurate but might be faster. You can see a summary in this table:

    Remember that Herbie's algorithm is randomized, so you likely won't see the exact same thing. That said, the most accurate alternative should be pretty similar.

    That alternative itself is shown lower down on the page:

    A couple features of this alternative stand out immediately. First of all, Herbie inserted an if statement. This if statement handles a phenomenon known as cancellation, and is part of why Herbie's alternative is more accurate. Herbie also replaced the square root operation with the hypot function, which computes distances more accurately than a direct square root operation.

    If you want to see more about how Herbie derived this result, you could click on the word "Derivation" to see a detailed, step-by-step explanation of how Herbie did it. For now, though, let's move on to look at another alternative.

    The fifth alternative suggested by Herbie is much less accurate, but it is about twice as fast as the original program:

    This alternative is kind of strange: it has two branches, and each one only uses one of the two variables xre and xim. That explains why it's fast, but it's still more accurate than the initial program because it avoids cancellation and overflow issues that plagued the original.

    Using Herbie's alternatives

    In this case, we were interested in the most accurate possible implementation, so let's try to use Herbie's most accurate alternative.

    // Herbie 2.1 for:
    //   0.5 * sqrt(2.0 * (sqrt(xre*xre + xim*xim) + xre))
    var r = Math.hypot(x.re, x.im);
    var re;
    if (xre + r <= 0) {
        re = 0.5 * Math.sqrt(2 * (x.im / (x.re / x.im) * -0.5));
    } else {
        re = 0.5 * Math.sqrt(2 * (x.re + r));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the Herbie query in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    Additionally, for some languages (e.g. JavaScript, Python, Wolfram, etc) you can use the drop-down in the top-right corner of the alternative block to translate Herbie's output to that language. However, you will still probably need to refactor and modify the results to fit your code structure, just like here.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/2.1/using-cli.html ================================================ Using Herbie from the Command Line

    Shell and Batch Mode

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    racket -l herbie shell
    Herbie 2.1 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore
      (x)
      ...
      1)

    Herbie suggests that 1 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell only shows Herbie's most accurate variant.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    racket -l herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie on 3 problems (seed: 1551571787)...
      1/3	[   0.882s]   30→ 0	Cancel like terms
    Warning: 24.7% of points produce a very large (infinite) output.
    You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/2.0/faq.html#inf-points> for more.
      2/3	[   1.721s]   29→ 0	Expanding a square
      3/3	[   2.426s]    0→ 0	Commute and associate

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

    ;; seed: 1551571787
    
    (FPCore (x) ... 1)
    (FPCore (x) ... (* x (- x -2)))
    (FPCore (x y z) ... 0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/2.1/using-web.html ================================================ Using Herbie from the Browser

    The Browser UI

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie. Run the Herbie web shell with this command:

    racket -l herbie web

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

    racket -l herbie web
    Herbie 2.1 with seed 1003430182
    Find help on https://herbie.uwplse.org/, exit with Ctrl-C
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    You can type an input expressions in standard mathematical syntax. After typing in an expression, you will be asked to specify the range of values for each input variable that Herbie should consider when trying to improve the expression. Hit the "Improve with Herbie" button once you're done to run Herbie.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    Batch report generation

    A report can also be generated directly from a file of input expressions in FPCore format:

    racket -l herbie report bench/tutorial.fpcore output/ 
    Starting Herbie on 3 problems (seed: 770126425)...
    Warning: 25.0% of points produce a very large (infinite) output. 
    You may want to add a precondition.
    See https://herbie.uwplse.org/doc/latest/faq.html#inf-points for 
    more.
      1/3   [   0.703s]   29→ 0     Expanding a square
      2/3   [   1.611s]    0→ 0     Commute and associate
      3/3   [   0.353s]   30→ 0     Cancel like terms

    This command generates a report from the input expressions in bench/tutorial.fpcore and saves the report in the directory output/. It's best if that directory doesn't exist before running this command, because otherwise Herbie may overwrite files in that directory.

    Occasionally you may also see Herbie emit warnings as shown above. All of Herbie's warnings are listed, with explanations, in the FAQ.

    The report Herbie generates is in HTML format so you'll need to start a web server. If you have Python installed, that's particularly convenient:

    python3 -m http.server -d output

    Then go to localhost:8000 in your favorite browser. The report summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see Herbie's output for them.

    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 parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/2.2/api-endpoints.html ================================================ Herbie HTTP API Endpoints

    HTTP API

    The Herbie API allows applications to interface with Herbie using HTTP requests. The API is designed to be stateless: the order in which endpoints are called shouldn't matter.

    Format for all endpoints

    All the endpoints listed below respond to POST requests unless otherwise specified. A typical example of sending a POST request to a running Herbie server is:

    curl -d \
      '{"formula": "(FPCore (x) (- (sqrt (+ x 1))))", "seed": 5}' \
      -H 'Content-Type: application/json' \
      http://127.0.0.1:8000/api/sample
        

    /api/sample

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        seed: <random seed for point generation>
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The sample endpoint allows the user to request a sample of points given the FPCore expression and a seed.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/exacts

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        sample: [point, ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The exacts endpoint allows the user to request the exact value of a set of points evaluated at a real number specification given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/calculate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The calculate endpoint allows the user to request the evaluation of a set of points evaluated at a floating-point implementation given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the evaluation of each point using the given FPCore as a floating-point implementation. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the evaluated value; the evaluated value of point n is points[n][1].

    /api/analyze

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      points: [[point, error], ... ]
    }

    The analyze endpoint allows the user to request error analysis of a set of point-exact pairs and a given floating-point implementation.

    Given a collection of points, their exact values, and an FPCore expression to analyze on, the analyze endpoint returns the error for each point for that expression. The error value returned is Herbie's internal error heuristic.

    /api/alternatives

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      alternatives: [alt, ... ],
      histories: [history, ... ],
      splitpoints: [splitpoint, ... ]
    }

    The alternatives endpoint allows the user to request rewrites from Herbie given an expression to rewrite and a set of point-exact pairs.

    Returns a list of alternatives represented by FPCore expressions through the "alternatives" field.

    Returns a list of derivations of each alternative through the "histories" field where history[n] corresponds to alternatives[n].

    Returns a list of splitpoints for each alternative through the "splitpoints" field where splitpoints[n] corresponds to alternatives[n]. splitpoints[n] will only contain information about the corresponding alternative.s splitpoints if the alternative is a branch-expression.

    /api/mathjs

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>
    }

    Response:

    {
      mathjs: <mathjs expression>
    }

    The mathjs endpoint allows the user to translate FPCore expressions into mathjs expressions.

    /api/cost

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        cost: cost
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 13120
    }

    Lower-Cost Example: (x+1)-(x)

    Request:

    {
        formula: <(FPCore (x) (- (+ x 1 ) x))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 320
    }

    The cost endpoint allows the user to request the evaluation of an expression's overall cost. Cost is calculated depending on the complexity of operations contained in the expression.

    Given an FPCore expression and a collection of points, returns the cost of the expression. The cost value returned is calculated internally by Herbie.

    The points should be of the same format as the points generated in the sample endpoint. Refer to /api/sample for more details.

    Note the sample points are not used in the cost calculation. The contents of the points do not matter as long as they are in the correct format as mentioned above.

    /api/translate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        language: "language"
    }

    Response:

    {
        result: "translated expression"
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        language: "python"
    }

    Response:

    {
        result: "def expr(x): return math.sqrt((x + 1.0)) - math.sqrt(x)"
    }

    The translate endpoint allows users to translate FPCore expressions into equivalent expressions in various programming languages.

    Given an FPCore expression and a target language, this endpoint returns the translated expression in the specified language. The language parameter should be a string representing the desired programming language. The response includes the translated expression.

    Currently supported languages are: python, c, fortran, java, julia, matlab, wls, tex, and js.

    ⚠️ Beta endpoints

    The endpoints below are currently a work in progress.

    /api/localerror

    Forthcoming.

    /api/timeline/{job-id}

    Retrieves the timeline data for a given API call. You may find the job id in either the JSON response or in the headers of the HTTP response for of the endpoints in Herbie.

    The timeline is an exception to the others in that it uses a GET request. Below is a sample of what the request might look like. You may consult the /infra/testApi.mjs file for current examples of how to use this API.

    curl -X GET \
      http://127.0.0.1:8000/api/timeline/0b95161a77fc3e29376bbb013d96c2827e2a1cd7
          
    ================================================ FILE: www/doc/2.2/diagrams.html ================================================ Diagrams

    Diagrams

    This page contains systems diagrams for Herbie.

    System diagram of Herbie
    High-level system diagram of Herbie. It highlights Herbie's core architecture, key external libraries (egg and Rival), and user input/output. Basic flow: Herbie passes user input (specification, precondition, etc.) to a sampler which computes the exact output for uniformly random input points. Herbie uses these exact outputs to compute the accuracy of candidate programs. The mainloop (scheduler) then alternates between generate and prune phases, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-prune loop is complete, Herbie extracts either output expressions using regime inference, which combines multiple candidate programs using conditionals. The resulting programs are summarized in a report.
    ================================================ FILE: www/doc/2.2/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    We recommend most users install Herbie from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker commands with sudo or run them as the administrative user.

    With Docker installed, you can run the Herbie shell with:

    docker run -it uwplse/herbie shell

    This will download the Herbie image and then run its shell tool.

    Running the web interface

    You can run the Herbie web server locally with

    docker run -it --rm -p 8000:80 uwplse/herbie

    and access the server at http://localhost:8000.

    (Herbie's Docker image binds to the container's port 80 by default; this command uses the -p 8000:80 option to expose that container port as the host's port 8000.)

    If you want to pass custom flags to the Herbie web server, make sure to also pass the --public and --port=80 flags to enable the Dockerized Herbie to talk to your computer. Make sure to access the server using HTTP, not HTTPS.

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    Loading custom platforms

    If you'd like to write and use custom platforms with Herbie in Docker, you'll need to mount the platform directory as well:

    docker run -it --rm \
        -v platform-dir:/platform \
        -u $USER \
        uwplse/herbie shell \
        --platform /platform/platform.rkt

    You can use custom platforms for the web interface or in batch mode using a similar approach.

    Building the Docker image

    This section is primarily of interest the Herbie developers.

    Clone the repo and confirm that Herbie builds correctly with make install. Next, examine the Dockerfile and Makefile together. The Dockerfile should follow a process exactly like the Makefile, except a clean initial environment is assumed.

    The build is split into 2 or more stages to limit the size of the resulting image. Each stage consists of a FROM command and a series of further commands to build up the desired environment, and later stages can refer to earlier stages by name—for example, COPY --from=earlier-stage ... can copy files compiled in earlier images.

    Before building the official image, bump the version of Rust used for binary compilation and the version of Racket used in production, and adjusting paths to match the newest version of the repo.

    Once you are ready, run this from the repository root:

    docker build -t uwplse/herbie:test .

    This builds a new test image and tags it uwplse/herbie:test. You can run this image with:

    docker run -p 8000:80 -it uwplse/herbie:test

    The web demo should now be visible at http://localhost:8000.

    To open a shell in a running container for testing, first get the container ID with:

    docker ps

    Then open a root shell in that container with

    docker exec -it <CONTAINER ID> sh

    The code and egg-herbie binaries should be under /src.

    ================================================ FILE: www/doc/2.2/error.html ================================================ What is Error?

    What is Error?

    Herbie helps you identify and correct floating point error in your numeric programs. But what is floating point error, and how does Herbie measure it?

    The summary

    When Herbie reports a "percentage accuracy" number like 92.3%, it's usually best to think of that as the percentage of floating-point inputs where the expression is reasonably accurate.

    The impact of this error on your application will depend a lot on which 7.7% of inputs are inaccurate, and what kind of error that is. You can find this out using the accuracy graph in Herbie reports. You can also use preconditions to restrict the inputs Herbie is considering.

    Why rounding matters

    In mathematics, we work with real numbers, but on a computer, we typically use floating-point numbers. Because there are infinitely many real numbers, but only finitely many floating-point numbers, some real numbers can't be accurately represented. This means that every time you do an operation, the true result will be rounded to a representable one.

    Take an extreme example: the code 1e100 + 1, which increments a huge number in IEEE 754 double-precision floating-point. There's an exact real-number answer, a one followed by 99 zeros and then another 1, but the closest floating-point value is the same as 1e100.

    Errors like this can cause problems. In the example above, the answers differ by one part per googol, which is pretty small. But the error can grow. For example, since 1e100 + 1 rounds to the same value as 1e100, the larger computation

    (1e100 + 1) - 1e100

    returns 0 instead of the correct answer, 1. Now the difference is pretty stark, and can grow even bigger through later operations.

    Bits of error

    There are lots of ways to measure how much rounding error there is in a computation. Most people find the notions of absolute and relative error most intuitive, but Herbie internally uses a more complex notion called bits of error.

    The bits of error metric imagines you have a list of all of the possible floating-point numbers, from largest to smallest. In that list, compare the floating-point value you computed to the one closest to the true answer. If they are the same, that's called 0 bits of error; if they are one apart, that's called one bit of error; three apart is two bits of error; seven apart is three bits; and so on.

    In general, if the two floating-point values are n items apart, Herbie says they have log2(n + 1) bits of error. Values like 0 and -0 are treated as having 0 bits of error, and NaN is considered to have the maximum number of bits of error against non-NaN values. While there's all sorts of theoretical justifications, Herbie mainly uses this error metric because we've found it to give the best results.

    On a single input, the best way to interpret the "bits of error" metric is that it tells you roughly how many bits of the answer, starting at the end, are useless. With zero bits of error, you have the best answer possible. With four bits, that's still pretty good because it's four out of 64. But with 40 or 50 bits of error, you're getting less accuracy out of the computation than even a single-precision floating-point value. And it's even possible to have something like 58 or 60 bits of error, in which case even the sign and exponent bits (which in double-precision floating-point the the most significant 12 bits) are incorrect.

    Percentage accuracy

    Because different number representations have different numbers of bits, Herbie shows the percentage of bits that are accurate instead of the bits of error. With double-precision floats, for example, 75% accurate means 16 bits of error.

    Bits of error measures the error of a computation for some specific input. But usually you're interested in the error of a computation across many possible inputs. Herbie therefore averages the accuracy percentage across many randomly-sampled valid inputs.

    Typically, input points are either very accurate or not accurate at all. So when computing percentage accuracy, Herbie's averaging a lot of points with near-100% accuracy and a lot of points with near-0% accuracy. In that sense, you can think of percentage accuracy as measuring mostly what percentage of inputs are accurate. If Herbie says your computation is 75% accurate what it's really saying is that about a quarter of inputs produce usable outputs.

    Valid inputs

    When Herbie computes this average, it's over valid, uniformly distributed input points.

    Herbie considers an input valid if it is a floating-point value in the appropriate precision and its true, real-number output 1) exists; 2) satisfies the user's precondition; and 3) can be computed. Let's dive into each requirement.

    1. An output can fail to exist for an input if something like a division by zero or square root of a negative number happens even with exact, real-number computation. Then there's no exact answer to compare against and the point is considered invalid.
    2. An input can fail to satisfy the user's precondition, which are usually a range of inputs. For example, if the precondition is (< 1 x 2), then the input x = 3 is invalid.
    3. Finally, and most rarely, Herbie can fail to compute the output for a particular input. For example, the computation (/ (exp 1e9) (exp 1e9)), which divides two identical but gargantuan numbers, does have an exact real-number answer (one), but Herbie can't compute that exact answer because the intermediate values are too large. This input is also invalid, but you'll get a warning if this happens.

    Herbie's percentage accuracy metric only averages over valid points. This means that when you change your precondition, you change which points are valid, and that can change the percentage accuracy reported by Herbie. This is useful: if you've observed a floating-point error, you can tailor your precondition to focus on inputs near the one you've observed.

    Infinite outputs can be valid. For example, consider (exp x) for x = 1e100. The true real-number result is some very large finite number. That real number, whatever it is, rounds to infinity in double-precision floating point. Herbie thus considers this input valid. Since you might find this surprising, Herbie issues a warning if too many outputs are infinite.

    Sampling inputs

    When randomly sampling inputs, Herbie considers each valid input equally likely. Importantly, this does not mean that it uses a uniform distribution, because floating-point values themselves are not uniformly distributed.

    For example, there are as many floating-point values between 1 and 2 as there are between one and one half, because floating-point values use an exponential encoding. But that means that, in the interval [0.5, 2], Herbie will sample from the first third of that range twice as often as from the other two thirds.

    This can produce unintuitive results, especially for intervals that cross 0. For example, in the interval [0, 1], the second half of that interval (from one half to one) has a tiny proportion of the weight (in double-precision floating-point, about 0.1%). If Herbie can improve accuracy by a little bit between zero and one half, while dramatically reducing accuracy between one half and one, it will think that that's an accuracy improvement. For this reason, Herbie prompts you to add a minimum absolute value for ranges that cross zero. Even a trivial minimum absolute value, like 1e-10, can dramatically improve Herbie's results.

    Unfortunately, there's no way for Herbie to intuit exactly what you mean, so understanding this error distribution is essential to understanding Herbie's outputs. For example, if Herbie reports that accuracy improved from 75% to 76%, it's essential to know: is the improvement happening on inputs between one half and one, or between 1e-100 and 2e-100? To answer this question, it's important to look over the reports and graphs generated by Herbie.

    ================================================ FILE: www/doc/2.2/faq.html ================================================ Herbie Warnings and Errors

    Common Warnings and Errors

    Herbie issues a variety of warnings and errors when something unexpected happens during compilation.

    Common warnings

    N% of points produce a very large (infinite) output.

    Sometimes, an input to your expression produces an output so large that it's best represented by a floating-point infinity. For example, (exp 1000) is over 10434, so it's much larger than the largest floating-point value. Herbie raises this warning when too many inputs (more than 20% of them) are this large. When you see this warning, you should usually set a more restrictive precondition.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10,000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for x = 1e100, absurdly large numbers would be required. Herbie discards these inputs and raises this warning. When you see this warning, you should usually set a more restrictive precondition.

    Could not uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a unique decimal representation for a given value. This is likely the result of a bug in a custom platform, likely in a representation definition. The platform needs to be fixed.

    Unused variable var

    The input FPCore contains a variable that is not used in the body expression. You should remove the unused variable.

    Unusual variable var

    The input expression contains a variable that is named similar to some named constant, like e instead of E. You should use a different variable name.

    Common errors

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized; (- (exp x) 1) is correct. Follow the input format more carefully.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. You can specify a more restrictive precondition or pass a larger value for the --num-analysis flag.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. You should fix either the precondition or the input program.

    ================================================ FILE: www/doc/2.2/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    Math format

    The Herbie web shell takes input in standard math syntax. More specifically, it uses a subset of the math.js syntax. The web shell automatically checks for syntax errors, and provides a graphical user interface for specifying the input domain. The web shell converts the mathematical expression and input ranges into FPCore before sending it to Herbie.

    FPCore format

    Herbie's command-line and batch-mode tools use FPCore format to describe mathematical expressions. FPCore looks like this:

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

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context.

    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))))

    The semicolon (;) character introduces a line comment. We recommend the .fpcore file extension for FPCore files.

    Supported functions

    FPCore expressions can use any of the following functions:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. Use - for both subtraction and negation.

    However, how Herbie evaluates these functions, their cost, and what additional functions are available depends on the platform you select.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison operators support chained comparisons with more than two arguments; for example (< 1 x 10) means 1 < x < 10.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    Unless you have a lot of Lisp experience, you'll probably find let* more intuitive.

    Internally, Herbie treats intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy. Using intermediate variables will therefore not produce a more accurate result or help Herbie run faster.

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will use the :spec expression to evaluate error, but use body expression as a starting-point for finding more accurate expressions.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point

    Platforms can also add additional precisions.

    Miscellaneous Input Properties

    A name can be provided before the argument list to name an FPCore. That FPCore can then be called in other, later FPCores.

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie allows :alt properties to specify additional "developer targets"; these might be other alternatives you've tried that you want to compare against.

    Herbie's benchmark suite also uses properties for continuous integration, but these are not officially supported and their use is discouraged.

    ================================================ FILE: www/doc/2.2/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. To start, install Racket. Then install Herbie, either from a package, from source, or via a Docker image.

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.10 are supported, but more recent versions are faster.

    On Linux, we recommend against installing Racket via Snap. If you must install Racket from Snap, install from source and store that source directory in your home directory or another allow-listed directory.

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

    racket
    Welcome to Racket v8.17 [cs].
    > (exit)

    Installing Herbie from a package

    Herbie can be installed as a binary package on x86 Windows and Linux and on x86 or ARM macOS. If you are on a more obscure platform, please install from source instead.

    After installing Racket, install Herbie from a package with:

    raco pkg install --auto herbie

    Then check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.2

    If you'd like, you can run Herbie with the herbie command by adding the following directory to your PATH (example paths for Racket 8.17):

    • On Windows, AppData\Roaming\Racket\8.17\bin in your user folder.
    • On macOS, Library/Racket/8.17/bin in your home folder.
    • On Linux, .local/share/racket/8.17/bin in your home directory.

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

    Installing Herbie from source

    Installing Herbie from source is best if you are a Herbie developer, or if you use a more obscure hardware/OS combination. The instructions assume a standard Unix userland; on Windows, you may have to install tools like Make separately, or use WSL.

    Install Racket as described above. Then install Rust 1.60.0 or later using rustup or some other means.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

    git clone https://github.com/herbie-fp/herbie

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other files and directories. Install Herbie with:

    make install

    This command installs Herbie and its dependencies and compiles it for faster startup. Check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.2

    You can add herbie to your path, as described in the package-install instructions. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like a virtual machine. We do not recommend using Herbie in Docker without prior Docker experience.

    The Docker documentation describes how to install and run our uwplse/herbie image.

    ================================================ FILE: www/doc/2.2/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    Herbie commands

    Herbie provides several subcommands, which offer interactive and batch modes for both the command line and the web interface:

    racket -l herbie web
    Use Herbie through your browser using a local server. This server can also be used from Odyssey.
    racket -l herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie prints faster and more accurate alternatives.
    racket -l herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    racket -l herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. Viewing these requires a web server.

    We recommend the web subcommand for interactive use with detailed reports that include graphs of error versus input values and plots comparing cost and accuracy. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie subcommand --help to view available command-line options for a subcommand. This command also shows undocumented subcommands not listed on this page.

    General options

    General options apply to all subcommands and are passed after the subcommand name but before other arguments, like this:

    racket -l herbie report --timeout 60 in.fpcore out/

    Options must go before subcommand arguments like input and output paths.

    --platform P
    Herbie's backend platform, which affects the operations available to Herbie, their accuracies, and their costs. The platform name is either one of the built-in platforms, or the path to a custom platform. In general, it's best to select the platform that most closely matches the programming language and hardware where you will be running floating-point code.
    --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 (not including the latter). Two runs of Herbie with the same seed should produce identical results. By default, a random seed is chosen.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given. By default, no timeout is used.
    --threads N
    Enables multi-threaded operation. By default, no threads are used. The argument is the number of threads to use, or yes to use all of the hardware threads.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of attempts Herbie makes to improve accuracy. The default, 4, suffices for most programs, and more iterations are rarely beneficial. But increase this option, say to 6, can sometimes lead to more accurate or faster results.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 12. Increasing this option will slow Herbie down, but may fix a "Cannot sample enough valid points" error.
    --num-enodes N
    The number of equivalence graph nodes to use when doing algebraic reasoning. The default is 4000. Increasing this option will slow Herbie down, but may improve results slightly.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --no-browser
    This flag disables the default behavior of opening the Herbie page in your default browser.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.) Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate programs.

    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

    Search options

    These options enable or disable transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    setup:search
    This option, on by default, uses interval subdivision search to help compute ground truth for complicated expressions. If turned off, Herbie will be slightly faster, but may hit the "Cannot sample enough valid points" error more often. Instead of turning this option off, try adjusting the --num-analysis flag.
    generate:rr
    This option, on by default, uses algebraic rewriting to generate candidate programs. This is Herbie's primary method of improving accuracy, and we do not recommend turning off this option.
    generate:taylor
    This option, on by default, uses series expansion to generate candidate programs. If turned off, Herbie will not use series expansion, which may help accuracy in some ranges while leaving Herbie unable to solve certain under- and overflow issues.
    generate:evaluate
    This option, on by default, uses arbitrary-precision arithmetic to generate candidate programs, specifically by exactly computing some constant expressions. If turned off, these exact computations won't be performed and Herbie won't be able to improve accuracy in those cases.
    generate:proofs
    This option, on by default, generates step-by-step derivations for HTML reports. If turned off, the step-by-step derivations will be absent, and Herbie will be slightly faster.
    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). Instead of turning this option off, consider increasing your platform's if cost to discourage branches.
    reduce:binary-search
    This option, on by default, uses binary search to refine the values used in inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.
    ================================================ FILE: www/doc/2.2/platforms.html ================================================ Herbie Platforms

    Writing a Herbie Platform

    Platforms define Herbie compilation targets. A platform might be specific to a programming language, to a math library, or to a hardware ISA. Writing a custom platforms can help Herbie produce faster and more accurate programs.

    Platform Concepts

    Herbie operates on mathematical specifications and floating-point expressions.

    Specifications are built from rational numbers, variables, and functions like +, sin, <, and if. A specification has a type, which is either real or bool.

    Types, functions, and specifications have floating-point analogs.

    Representations are the floating-point analogs of types, and Herbie comes with three built in: <binary32> and <binary64> are reprentations of real and correspond to single- and double-precision IEEE-754 arithmetic. There's also a <bool> representation for booleans. It's possible to define new representations, described on another page.

    Operations are the floating-point analog of functions and represent the actual floating-point operation the compilation target can perform. There are typically several operations for each supported function; for example, in the C standard library provides cosf and cos, both of which correspond to the cos function but for different representations (<binary32> and <binary64>).

    Expressions are the floating-point analog of specifications and represent target-specific floating-point computations. Platforms, to put it simply, just define the set of representations and operations that expressions are allowed to use. Herbie then searches for a fast and accurate expression that corresponds to the user's input specification.

    Each representation, operation, and thus expression has a cost, which is a non-negative real number. Generally, the cost of a representation is the time it takes to read a value of that representation from memory and the cost of an operation is the time it takes to execute that operation. However, in principle, platforms can use cost to represent another metric like area or latency. Only relative costs matter. If you're not sure, just putting "1" for all the costs is not a bad place to start.

    Defining a Platform

    A platform definition is a text file that starts with:

    #lang herbie/platform

    Herbie can then be informed to use this platform by passing the --platform path/to/file command-line argument.

    The file contains Racket code. That code can define the platform's representations and operations using define-representation and define-operation. It can also define variables and helper functions, import external packages, or anything else Racket can do. If necessary, it can define new representations.

    Herbie's built-in platforms are good example platforms to study or modify. If you use them as an example, you'll need to change the #lang line at the top of the file to be herbie/platforms; the built-in platforms are different because they are built in to Herbie and can't assume Herbie is installed.

    Defining Representations

    The typical platform starts by defining the representations it uses and their costs with define-representation:

    (define-representation <bool> #:cost 1)
    (define-representation <binary64> #:cost 1.5)

    This cost is the cost for reading a variable or literal of that representation. Note that platforms must explicitly define a cost for the <bool> representation if it uses booleans. If the platform forgets to define a representation that it uses, Herbie will produce an error when loading the platform.

    If the same cost is used repeatedly, it can be convenient to define a variable:

    (define cost 1)
    (define-representation <bool> #:cost cost)

    After defining the representations it uses, a platform then defines all the operations it supports.

    Defining Operations

    An operation is defined by four fields:

    • A signature, which gives the operation's name and its input and output representations.
    • A specification for the operation's mathematical behavior.
    • An implementation that computes the operation's output given concrete inputs.
    • A cost for using the operation in an expression.

    The define-operation construct requires exactly these four fields:

    (define-operation (recip [x <binary32>]) <binary32>
      #:spec (/ 1 x) #:impl (lambda (x) ...) #:cost 3)

    The first line gives the operation's signature: it is named recip, it has one single-precision input x, and it outputs a single-precision float.

    The #:spec field gives this operation's specification as (/ 1 x), one divided by x. In other words, this operation computes a number's reciprocal.

    The #:impl field gives this operation's implementation, as a Racket function (defined with lambda). An operation's implementation is a Racket function with as many arguments as the operation. It is called with concrete inputs in the corresponding input representations, and must return an output in the output representation. It can be defined using a lambda, a define block, or any other function-defining Racket construct.

    When an implementation function is called, <binary64> and <binary32> arguments are passed as Racket flonums, while <bool> arguments are passed as Racket booleans. Single-precision numbers aren't a separate type in Racket. instead, you create them from double-precision floats by calling flsingle.

    For this example, to compute a 32-bit reciprocal for a 32-bit input, one could use (flsingle (/ 1.0 x)) for the body of the lambda.

    The #:cost field gives this operation's cost, 3.

    Defining Multiple Operations

    Realistic platform usually have a lot of similar operations: addition, subtraction, multiplication, and division, or sine, cosine, tangent, and so on. The define-operations construct defines multiple operations at a time, as long as they have the same input and output representations:

    (define-operations ([x <binary64>] [y <binary64>]) <binary64>
        [+ #:spec (+ x y) #:impl + #:cost 0.200]
        [- #:spec (- x y) #:impl - #:cost 0.200]
        [* #:spec (* x y) #:impl * #:cost 0.250]
        [/ #:spec (/ x y) #:impl / #:cost 0.350])

    This block defines four functions, each with their own name, specification, implementation, and cost. Note that in this case the #:impl column refers to the Racket functions +, -, *, and /.

    Common Kinds of Operations

    This section lists common kinds of operations and notes things you should keep in mind when defining them.

    Math Library Functions

    Most operating systems have a standard libm library that provides elementary functions like cos. You can use Herbie's from-libm helper to load implementations from libm:

    (define-operation (fabs.f32 [x <binary32>]) <binary32>
      #:spec (fabs x) #:impl (from-libm 'fabsf) #:cost 0.125)

    The from-libm uses dynamic linking to load libm, extracts the symbol passed to from-libm, and then uses the operation's signature to call into the dynamically-linked library from Racket. Must make sure to pass the correct symbol name; for single-precision functions, make sure to add the "f" suffix.

    Numeric Variations

    Some platforms provide numeric variations like log1p or cosd for common functions. You can define operations for them using complex specifications:

    (define-operation (cosd [x <binary64>]) <binary64>
      #:spec (cos (* x (/ (PI) 180))) #:impl (lambda (x) ...) #:cost 4)

    The #:spec in this example explains to Herbie that cosd is the cosine of x in degrees. Herbie will then use cosd when that improves accuracy.

    Other common numeric variations include fma, log1p, expm1, and hypot. If they're available on your target, we strongly recommend defining operations for them; they often improve accuracy by quite a bit!

    Conversions

    A conversion or cast operation uses mixed representations:

    (define-operation (64->32 [x <binary64>]) <binary32>
      #:spec x #:impl flsingle #:cost 1)

    This operation has a 64-bit input and a 32-bit output. Its specification is just x, which means it doesn't actually do anything mathematically. The implementation is the standard Racket flsingle function (which converts from double- to single-precision) and it has a cost of 1.

    Herbie will use this conversion operation for casting between the two types.

    Comparisons

    Comparison operations return <bool>:

    (define-operations ([x <binary64>] [y <binary64>]) <bool>
      [==.f64 #:spec (== x y) #:impl =          #:cost 1]
      [!=.f64 #:spec (!= x y) #:impl (negate =) #:cost 1]
      [<.f64  #:spec (< x y)  #:impl <          #:cost 1]
      [>.f64  #:spec (> x y)  #:impl >          #:cost 1]
      [<=.f64 #:spec (<= x y) #:impl <=         #:cost 1]
      [>=.f64 #:spec (>= x y) #:impl >=         #:cost 1])

    Here, negate is a Racket function that negates a comparison function.

    A platform only needs to provide the comparison functions available on the target. However, Herbie's "regimes" pass uses the <= operation, so it's best to provide one if one is available.

    A platform that uses both representation needs to define separate <binary32> and <binary64> comparison operations. They can have different costs.

    Conditionals

    Conditionals can be defined as operations with a boolean argument:

    (define-operation (if-f64 [c <bool>] [t <binary64>] [f <binary64>]) <binary64>
      #:spec (if c t f) #:impl if-impl
      #:cost (if-cost 14) )

    Here if-impl is a Herbie-provided Racket function that wraps a standard if statement, while the if inside the #:spec is how specifications refer to mathematical conditional expressions.

    Conditional operations usually pass a procedure to #:cost. The helper if-cost returns a procedure that receives the costs of the arguments and combines them into a total cost. It computes the cost of evaluating the condition plus the larger of the two branch costs. In this example we add a constant cost of 14 to that value.

    A platform needs to define both <binary32> and <binary64> conditional operations if it uses both representations. They can have different costs. There's typically no need to define conditional operations for <bool> as Herbie does not rewrite boolean expressions.

    Constants

    Mathematical constants like E and PI are considered operations in Herbie; they just have no inputs. For example, to define a 64-bit inverse-π constant, write:

    (define-operation (INVPI) <binary64>
      #:spec (/ 1 (PI)) #:impl (lambda () ...) #:fpcore PI #:cost 0.5)

    Note the parentheses in various fields. The name INVPI has parentheses around it like all operation signatures; it just doesn't have any arguments after the name. In the #:spec, the PI constant is also wrapped in parentheses because it is also treated as a zero-argument function. And in the #:impl, the implementation is given by a Racket function of no arguments. You can also use the Racket const function to construct these no-argument functions.

    But in the #:fpcore field the PI value doesn't use parentheses, because FPCore treats constants as constants, not zero-argument functions.

    Constants can be defined in any precision. If you want the same constant to be available in multiple precisions, you have to define multiple constant operations.

    Negation

    Herbie's specification language has a negation function, and it's usually a good idea to define a negation operation if your target has one. Define it like this:

    (define-operation (neg.f32 [x <binary64>]) <binary64>
        #:spec (neg x) #:impl - #:fpcore (- x) #:cost 0.125)

    Here, in the #:spec, (neg x) refers to the negation function in Herbie's specification language, while the - symbol after #:impl refers to Racket's subtraction function, which also acts as a negation function.

    There is also an #:fpcore field. This field tells Herbie how to represent this operation in FPCore (for user input and output). The default #:fpcore is the operation's #:spec but there are a few functions (like negation) where Herbie's specification language uses a different syntax from FPCore and #:fpcore needs to be specified manually.

    Precision-specific Variations

    If a platform supports both <binary32> and <binary64>, they often support similar operations:

    (define-operations ([x <binary32>]) <binary32>
        #:fpcore (! :precision binary32 _)
        [fabs.f32 #:spec (fabs x) #:impl (from-libm 'fabsf) #:cost 0.125]
        [sin.f32  #:spec (sin x)  #:impl (from-libm 'sinf)  #:cost 4.250]
        ...)
      (define-operations ([x <binary64>]) <binary64>
        #:fpcore (! :precision binary64 _)
        [fabs.f64 #:spec (fabs x) #:impl (from-libm 'fabs)  #:cost 0.125]
        [sin.f64  #:spec (sin x)  #:impl (from-libm 'sin)   #:cost 4.200]
        ...)

    Here, two define-operations blocks define two sets of functions with different signatures but identical specifications. To disambiguate these functions in FPCore output, the #:fpcore argument to define-operations defines different FPCore properties for each set of operations. In that argument, the underscore is replaced by each operation's #:spec.

    Hard-to-emulate Operations

    Sometimes a platform will offer an operation that's difficult to implement accurately. In this case, Herbie's from-rival helper can provide a slow but correctly-rounded implementation:

    (define-opertion (dotprod [a <binary64>] [b <binary64>]
                                [c <binary64>] [d <binary64>]) <binary64>
      #:spec (+ (* a b) (* c d)) #:impl (from-rival) #:cost 1.25)

    The from-rival helper uses the MPFR library to evaluate the operation's specification. Compilation is usually much slower than with a native floating-point implementation, but for unusual operations that are difficult to implement otherwise, it can still allow compilation to proceed.

    Note that from-rival implementations are always "correctly-rounded", meaning as accurate as possible. Most targets don't actually offer correctly-rounded operations, which can mean that Herbie's outputs won't be as accurate as Herbie assumes. It's always better to execute the actual operation on the actual target if possible, so as to precisely emulate its actual behavior.

    ================================================ FILE: www/doc/2.2/plugins.html ================================================ Herbie Other APIs

    Other Herbie APIs

    Herbie platforms are the main way to customize Herbie's behavior. The typical platform just defines the set of representations and operations that available to Herbie when compiling. However, platform files can technically contain arbitrary Racket code, and thus can call other Herbie APIs, including importing external libraries, defining new representations, and so on. This page documents such APIs. Note that a level of comfort with Racket is assumed.

    Please note that all of the APIs on this page are considered unstable and may change version to version. If you run into issues, please file a bug.

    Defining representations

    Representations what Herbie calls different number formats. They play a central role in platforms. Concretely, a representation is a set of Racket values that represent both real numbers and bit patterns.

    Specifically, a representation value needs to be convertible to Racket bigfloat values (which are basically MPFR floats) and also to ordinals, meaning integers between -2w−1 and 2w−1−1 for some bit width w.

    Create representations with make-representation:

    (make-representation
    #:name name
    #:total-bits width
    #:bf->repr bf->repr
    #:repr->bf repr->bf
    #:ordinal->repr ordinal->repr
    #:repr->ordinal repr->ordinal
    #:special-value? special?)

    The #:name should be either a symbol, or a list containing symbols and integers. The #:total-bits value should be a positive integer. The #:total-bits parameter determines the total ordinal range your format takes up, not just its significand range, so for example for double-precision floats you need a #:total-bits of 64.

    The #:bf->repr and #:repr->bf values should be that convert between representation values and Racket bigfloats. The repr->bf function should use as large a bigfloat precision as needed to exactly represent the value, while the bf->repr function should round as per the current value of the bf-rounding-mode parameter.

    All non-NaN bigfloat values should result in non-NaN representation values. For example, (bf->repr (bf "1e1000000")) should yield the largest real value in the representation. Infinite values, as in (bf->repr +inf.bf), should be interpreted as really large real values, not as infinite values. For example, the posit format has an "infinite" value, but it behaves more like a NaN, so converting bigfloat infinity to a posit should yield its largest real value instead.

    The #:ordinal->repr and #:repr->ordinal functions represent ordinals as Racket integers between -2width−1 (inclusive) and 2width−1 (exclusive). Ordinals must be in real-number order; that is, if (repr->bf x) is less than (repr->bf y), then (repr->ordinal x) should be less than (repr->ordinal y).

    The #:special function should return true for NaN values (or whatever your representation calls values that don't represent any real number) and false for all other values. Special values can be anywhere in the ordinal range, and you can have as many or as few of them as you want.

    make-representation returns a representation object, which you can then use in define-representation and define-operation.

    Defining Generators

    Generators are helper functions for generating implementations in define-operation. For example, from-libm and from-rival are generators.

    To define a generator, use define-generator:

    (define-generator ((from-foo args ...) spec ctx)
    body ...)

    Here, from-foo is the name of your generator, and args are any additional arguments the generator takes. For example, from-libm takes one argument, the symbol name.

    Then, inside the body, you can use those arguments as well as spec and ctx, to construct an actual implementation function.

    The specification spec is a Racket tree made up of lists and symbols and numbers.

    The signature ctx is "context" object; you can access its context-repr to get the operation's output representation, its context-vars to access its variable names (as a list of symbols), and its context-var-reprs to access its input representations (as a parallel list of representations). The context-lookup function can be used to look up a input argument's representation by name.

    ================================================ FILE: www/doc/2.2/release-notes.html ================================================ Herbie 2.2 Release Notes

    Herbie 2.2 Release Notes

    The Herbie developers are excited to announce Herbie 2.2! This release focuses extensible compilation targets using platforms.

    What is Herbie? Herbie compiles mathematical expressions to fast and accurate floating point programs, avoiding the bugs, errors, and surprises of floating point arithmetic. Visit the main page to learn more.

    Releasing the Platforms API

    (define-operation (cosd [x <binary64>]) <binary64>
              #:spec (cos (* x (/ (PI) 180)))
              #:impl (from-rival)
              #:cost 12.5)
    Herbie 2.2's platforms allow you to define custom compilation targets, including novel hardware (CPUs, GPUs, FPGAs, TPUs), programming languages (Julia, Matlab, Fortran), or software libraries (Numpy, Eigen, cuBLAS). Platforms can define new number formats, new floating-point operations on those formats, and new cost models for those operations. Herbie will automatically optimize its results for your platform.

    Last year, Herbie 2.1 included the beginnings of platforms, Herbie's API for multiple backends. We've simplified and improved this API, and are now ready to release it.

    In short, platforms allow you define the functions Herbie is allowed to use when compiling floating-point programs. A platform can expose Python's fsum, Julia's cosd, or AVX's rcpps to Herbie, which can then use those functions to produce even faster and more accurate output. Our recent publications demonstrate dramatic accuracy and performance improvements using platforms.

    Platforms are a large change, and we invite users to try it out. The platforms documentation explains how to select different built-in platforms, or even how to write your own.

    Besides the new feature, platforms allow us to deprecate a variety of now-superseded features:

    • The default platform now uses a realistic, auto-tuned cost model which should lead to faster code in practice.
    • The Herbie 2.0 and 2.1 cost models are replaced by the herbie20 platform.
    • The --no-pareto flag is replaced by the herbie10 platform.
    • Plugins are now replaced by ordinary Racket libraries imported directly by plugins.
    • Preprocessing is now replaced by standard platform-provided functions such as fmin, fmax, fabs, and copysign.

    Restructuring the Main Loop

    The new Herbie main loop.
              The simplify step and patch table have been removed,
              while the compute phase has been added.
    The Herbie main loop is dramatically simplified, with no separate simplify or localize phase and a new compute phase that aid with hard-to-compute constants.

    We've overhauled the Herbie main loop, which is the high-level algorithm that invokes different Herbie sub-systems to generate and filter different candidate programs. This overhaul both introduces new systems, which should make Herbie more capable, and reduce duplicative work, dramatically speeding up Herbie. Specifically:

    • A new compute phase was added, which replaces variable-free subexpressions with constants. The constants are computed using high-precision arithmetic and are guaranteed to be exact.
    • The simplification phase was removed. This phase mostly duplicated work already done by the rewrite phase, so removing it had very little impact on performance and accuracy of generated code, but reduced Herbie runtime significantly.
    • The localize phase was also removed. This phase existed to reduce duplicate work in other phases; batches deduplicate the work automatically so localize is no longer needed. Removing this phase also significantly sped up Herbie.
    • Minor phases like initial and final simplify were removed as well. Most importantly, this allowed replacing the existing, complex "accelerator" API (never fully released) and replace it with the much simpler "platform" API.
    • Herbie more correctly tracks Taylor approximations inside Herbie, and will now avoid making "approximations to approximations" leading to runaway error in rare cases.

    Flattening Expressions to Batches

    %0 = x
    %1 = (literal 1)
    %2 = (+ %0 %1)
    %3 = (* %2 %2)
    %4 = (- %3 %1)
    %5 = (neg %2)
    %6 = (* %5 %5)
    %7 = (- %6 %1))
    An example of a batch, containing two expressions (and their subexpressions): (x + 1)2 - 1 and (-(x + 1))2 - 1. Shared subexpressions are only represented once in the batch, and are only processed once by Herbie.

    We have also changed Herbie's main data structure for representing programs. Until now, Herbie represented programs via their abstract syntax tree. While simple and effective, this representation lead to processing identical subexpressions repeatedly. This was especially problematic for larger programs, which can sometimes have the same subexpression appear exponentially many times.

    The batch data structure instead uses a flat array with back-references to represent programs, which means that duplicate subexpressions appear and are processed just once. This lead to significant speedups throughout Herbie:

    • Batches automatically deduplicates identical candidate programs, which occur when multiple Herbie phases produce the same output. There were a lot more of these than we thought; in some cases as much as 25× deduplication occurs.
    • The series phase, now uses batches internally, allowing Herbie to consider many more series expansions.
    • Herbie's FFI layer for interacting with the egg library now leverages batches to reduce time spent copying data in and out of egg.
    • In some cases, one batch is shared across multiple phases (like evaluation and pruning), reducing duplication even further.
    • The derivations phase was dramatically sped up by caching proof objects across multiple candidate programs. Additionally, a debugging pass called "soundiness" was removed entirely, since the problems it was built to debug no longer occur. Thanks to both changes, derivations take almost no time at all any more.

    Sister Projects

    The Odyssey numerics workbench is releasing version 1.2 today, featuring a new "local error" component and various design tweaks and improvements. Odyssey can be tried from its online demo or installed locally from the VS Code Marketplace.

    The Rival real-arithmetic package is releasing version 2.2 today, including various tweaks, optimizations, and improvements. Most significantly, a new "hints" feature significantly speeds up sampling expressions that use fmin, fmax, and if.

    Other improvements

    • Herbie now offers a basic HTTP API, including both synchronous and asynchronous jobs and access to various internal Herbie features. This HTTP API is a work in progress and is primarily built to support Odyssey. However, our work on the API has also enabled the standard Herbie web interface to support threads, which should make Herbie faster in a lot of common use cases.
    • Small quality-of-life changes have been made to the Herbie reports, including hiding duplicate alternatives, updating various dependencies, and generating the reports more quickly.
    • Optional, off-by-default support for the egglog rewriting engine has been added. We are excited about growth and competition among rewrite engines and hope to continue driving forward this research area.
    • Fixing a variety of small bugs, such as nondeterminism due to an incorrect type signature for fma and new rewrite rules to unlock more accurate results.
    • Many general-purpose cleanups, including removing a lot of now-unnecessary nightly infrastructure, adoption of a new formatting guideline, reorganization of the source code, and new debug infrastructure.

    Try it out!

    We want Herbie to be 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 or contribute.


    If you find Herbie useful, let us know!

    ================================================ FILE: www/doc/2.2/report.html ================================================ Herbie reports

    Herbie reports

    When used in the browser, Herbie generates HTML reports full of information about the accuracy and relative speed of the initial and alternative expressions.

    The top of the report page has a right-hand menu bar with additional links. “Metrics” give you detailed internal information about Herbie, while “Report”, if present, takes you back to the full Herbie report.

    Below the menu lies a brief summary of the results. Herbie can produce multiple alternatives to the initial program, and this summary gives their basic statistics.

    Summary numbers from a Herbie report.
    Percentage Accurate
    The percentage accuracy of the initial program and what Herbie thinks is its most accurate alternative.
    Time
    The time it took Herbie to generate all of the alternatives.
    Alternatives
    The number of alternatives found.
    Speedup
    The speed, relative to the initial program, of the fastest alternative that improves accuracy.

    Specification

    Next, the specification that you gave to Herbie. This section is closed by default. Typically, the specification is also the initial program, but in some cases, like if the :spec property is used, they can differ. The specification also includes any preconditions given to Herbie.

    You can use the drop-down in the top left to display the specification in an alternative syntax.

    Local Percentage Accuracy graph

    Next, the Local Percentage Accuracy graph compares the accuracy of the initial program to Herbie's most accurate alternative. 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. You can add a precondition to restrict Herbie to the more important inputs in that case.

    In the plot, each individual sampled point is shown with a faint circle, and the thick line is moving average of those individual samples. The red line is the initial program and the blue line is Herbie's most accurate alternative.

    Accuracy is shown on the vertical axis, and higher is better. The horizontal axis shows one of the variables in the input program; the dropdown in the title switches between input variables. The checkboxes below the graph toggle the red and blue lines on and off.

    If Herbie decided to insert an if statement into the program, the locations of those if statements will be marked with vertical bars.

    Accuracy vs Speed

    Next, a Pareto graph and table list the alternatives Herbie found.

    Both the plot and the table show the same data. In the plot, accuracy is on the vertical axis and speedup is on the horizontal axis. Up and to the right is better. The initial program is shown with a red square, while each of Herbie's alternatives is shown with a blue circle. A faint line shows the Pareto frontier—that is, it goes through all Herbie alternatives that maximize speed for their level of accuracy. Some of Herbie's alternatives may not be on the Pareto frontier due to differences between the training and test set.

    In the table, each alternative is shown along with its accuracy and speed relative to the initial program. Values are green if they are better than the initial program, and black otherwise. Each alternative is linked to its description lower on the page.

    Initial program and Alternatives

    Below the table come a series of boxes detailing the initial program and each of Herbie's alternatives, along with their accuracy and relative speed.

    The accuracy and relative speed of each alternative is given in the title. Below the title, the alternative expression itself is given. The dropdown in the top right can be used to change the syntax used. If Herbie could not come up with anything better than the initial program, no alternatives are displayed.

    Each alternative also has a derivation, which can be shown by clicking on "Derivation". The derivation shows each step Herbie took to transform the initial program into this alternative. The initial program has no derivation.

    Each step in the derivation gives the accuracy after that step. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program. The derivation will also call out any case splits. When a part of the step is colored blue, that identifies the changed part of the expression.

    Derivations may also contain "step-by-step derivations"; you can click on those step-by-step derivations to expand them. Each step in the step-by-step derivation names an arithmetic law from Herbie's database, with metadata-eval meaning that Herbie used direct computation in a particular step.

    Derivations are intended to give you more confidence in Herbie's results and are not guaranteed to be an accurate reflection of Herbie's internal process of constructing an alternative.

    Reproduction

    Finally, Herbie gives a command you can run to reproduce its result. If you find a Herbie bug, include this code snippet when filing an issue.

    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.

    ================================================ FILE: www/doc/2.2/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/2.2/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 in floating-point. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using it.

    Giving Herbie expressions

    Start Herbie with:

    racket -l herbie web

    Alternatively, if you added herbie to the path, you can always replace racket -l herbie with just herbie.

    After a brief wait, your web browser should open and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Let's start by just looking at an example of Herbie running. Click "Show an example". This will pre-fill the expression sqrt(x + 1) - sqrt(x) with x ranging from to 0 and 1.79e308.

    The input range field in the Herbie web UI.

    Now that you have an expression and a range for each variable, click the "Improve with Herbie" button. You should see the entry box gray out, and shortly thereafter some text should appear describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results.

    The very top of this results page gives some quick statistics about the alternative ways Herbie found for evaluating this expression:

    Statistics and error measures for this Herbie run.

    Here, you can see that Herbie's most accurate alternative has an accuracy of 99.7%, much better than the initial program's 53.2%, and that in total Herbie found 5 alternatives. One of those alternatives is both more accurate than the original expression and also 1.9× faster. The rest of the result page shows each of these alternatives, including details like how they were derived. These details are all documented, but for the sake of the tutorial let's move on to a more realistic example.

    Programming with Herbie

    You can use Herbie on expressions from source code, mathematical models, or debugging tools. But most users use Herbie as they write code, asking it about any complex floating-point expression they write. Herbie has options to log all the expressions you enter so that you can refer to them later.

    But to keep the tutorial focused, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need to start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small kernel that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or prints results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. We're interested in the complex square root function, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, different types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total, this code turns into four Herbie inputs: two output fields, for each of the two branches.

    Let's focus on the first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative (but it runs for all values of x.re). So, select the full range of values for x.re, but restrict the range of x.im, like this:

    Restricting the input range to xim >= 0.
    This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression. The number 1.79e308 is approximately the largest double-precision number, and will auto-complete.

    Herbie's results

    Herbie will churn for a few seconds and produce a results page. In this case, Herbie found 4 alternatives, and we're interested in the most accurate one, which should have an accuracy of 84.6%:

    Below these summary statistics, we can see a graph of accuracy versus input value. By default, it shows accuracy versus xim; higher is better:

    The drop in accuracy once xim is bigger than about 1e150 really stands out, but you can also see that Herbie's alternative more accurate for smaller xim values, too. You can also change the graph to plot accuracy versus xre instead:

    This plot makes it clear that Herbie's alternative is almost perfectly accurate for positive xre, but still has some error for negative xre.

    Herbie also found other alternatives, which are less accurate but might be faster. You can see a summary in this table:

    Herbie's algorithm is randomized, so you likely won't see the exact same thing; you might see more or fewer alternatives, and they might be more or less accurate and fast. That said, the most accurate alternative should be pretty similar.

    That alternative itself is shown lower down on the page:

    A couple features of this alternative stand out immediately. First of all, Herbie inserted an if statement. This if statement handles a phenomenon known as cancellation, and is part of why Herbie's alternative is more accurate. Herbie also replaced the square root operation with the hypot function, which computes distances more accurately than a direct square root operation.

    If you want to see more about how Herbie derived this result, you could click on the word "Derivation" to see a detailed, step-by-step explanation of how Herbie did it. For now, though, let's move on to look at another alternative.

    The fifth alternative suggested by Herbie is much less accurate, but it is about twice as fast as the original program:

    This alternative is kind of strange: it has two branches, and each one only uses one of the two variables xre and xim. That explains why it's fast, but it's still more accurate than the initial program because it avoids cancellation and overflow issues that plagued the original.

    Using Herbie's alternatives

    In this case, we were interested in the most accurate possible implementation, so let's try to use Herbie's most accurate alternative.

    // Herbie 2.1 for:
    //   0.5 * sqrt(2.0 * (sqrt(xre*xre + xim*xim) + xre))
    var r = Math.hypot(x.re, x.im);
    var re;
    if (xre + r <= 0) {
        re = 0.5 * Math.sqrt(2 * (x.im / (x.re / x.im) * -0.5));
    } else {
        re = 0.5 * Math.sqrt(2 * (x.re + r));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the Herbie query in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, including JavaScript, you can use the drop-down in the top-right corner of the alternative block to translate Herbie's output to that language. However, you will still probably need to refactor and modify the results to fit your code structure, just like here.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/2.2/using-cli.html ================================================ Using Herbie from the Command Line

    Shell and Batch Mode

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    racket -l herbie shell
    Starting Herbie 2.2 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore (x)
      ...
      1.0)

    Herbie suggests that 1.0 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell only shows Herbie's most accurate variant.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    racket -l herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie 2.2 with seed 1139794558...
    Warning: 75.2% of points produce a very large (infinite) output. You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/2.0/faq.html#inf-points> for more.

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

    ;; seed: 1139794558
    
    (FPCore (x)
      :name "Cancel like terms"
      ...
      1.0)
    
    (FPCore (x)
      :name "Expanding a square"
      ...
      (fma x x (+ x x)))
    
    (FPCore (x y z)
      :name "Commute and associate"
      ...
      0.0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/2.2/using-web.html ================================================ Using Herbie from the Browser

    The Browser UI

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie.

    Start the Herbie web shell by running:

    racket -l herbie web

    After a few seconds, the web shell will start up and direct your browser to Herbie:

    racket -l herbie web
    Starting Herbie 2.2 with seed 1003430182
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    You can type an input expressions in standard mathematical syntax. After typing in an expression, you will be asked to specify the range of values for each input variable. Once you're done, hit the "Improve with Herbie" button to run Herbie.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    Batch report generation

    A report can also be generated directly from an input file in FPCore format:

    racket -l herbie report bench/tutorial.fpcore output/ 
    Starting Herbie 2.2 with seed 770126425...
    Warning: 25.0% of points produce a very large (infinite) output. You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/latest/faq.html#inf-points> for more.
      1/3	[   0.8s]   55% → 100%	Expanding a square
      2/3	[   0.8s]  100% → 100%	Commute and associate
      3/3	[   0.9s]   53% → 100%	Cancel like terms

    This command generates a report from the input expressions in bench/tutorial.fpcore and saves the report in the directory output/. It's best if that directory doesn't exist before running this command, because otherwise Herbie may overwrite files in that directory.

    Occasionally Herbie will emit warnings, just like in the example above. All of Herbie's warnings are documented, with explanations and suggested fixes.

    The report Herbie generates is in HTML format so you'll need to start a web server. If you have Python installed, that's particularly convenient:

    python3 -m http.server -d output

    Then go to localhost:8000 in your favorite browser. The report summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see Herbie's output for them.

    Batch report generation is the best way to run Herbie on a large collection of inputs. Like the web shell, it can be customized through command-line options, including parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc/2.3/api-endpoints.html ================================================ Herbie HTTP API Endpoints

    HTTP API

    The Herbie API allows applications to interface with Herbie using HTTP requests. The API is designed to be stateless: the order in which endpoints are called shouldn't matter.

    Format for all endpoints

    All the endpoints listed below respond to POST requests unless otherwise specified. A typical example of sending a POST request to a running Herbie server is:

    curl -d \
      '{"formula": "(FPCore (x) (- (sqrt (+ x 1))))", "seed": 5}' \
      -H 'Content-Type: application/json' \
      http://127.0.0.1:8000/api/sample
        

    /api/sample

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        seed: <random seed for point generation>
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The sample endpoint allows the user to request a sample of points given the FPCore expression and a seed.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/exacts

    Example input & output

    Request:

    {
        formula: <FPCore expression>,
        sample: [point, ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The exacts endpoint allows the user to request the exact value of a set of points evaluated at a real number specification given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the exact evaluation of each point with the given spec. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the exact evaluation; the exact value of point n is points[n][1].

    Herbie calculates the "ground truth" by calculating the values with more precise numbers. This can be slow.

    /api/calculate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        points: [[point, exact], ... ]
    }

    The calculate endpoint allows the user to request the evaluation of a set of points evaluated at a floating-point implementation given as an FPCore expression.

    Some points may not be calculable given the FPCore expression.

    Returns a collection of points and the evaluation of each point using the given FPCore as a floating-point implementation. The results are returned through the "points" field and are represented by an array of point-exact pairs with the first value representing the point and the second value representing the evaluated value; the evaluated value of point n is points[n][1].

    /api/analyze

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      points: [[point, error], ... ]
    }

    The analyze endpoint allows the user to request error analysis of a set of point-exact pairs and a given floating-point implementation.

    Given a collection of points, their exact values, and an FPCore expression to analyze on, the analyze endpoint returns the error for each point for that expression. The error value returned is Herbie's internal error heuristic.

    /api/alternatives

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>,
      sample: [[point, exact], ... ]
    }

    Response:

    {
      alternatives: [alt, ... ],
      histories: [history, ... ],
      splitpoints: [splitpoint, ... ]
    }

    The alternatives endpoint allows the user to request rewrites from Herbie given an expression to rewrite and a set of point-exact pairs.

    Returns a list of alternatives represented by FPCore expressions through the "alternatives" field.

    Returns a list of derivations of each alternative through the "histories" field where history[n] corresponds to alternatives[n].

    Returns a list of splitpoints for each alternative through the "splitpoints" field where splitpoints[n] corresponds to alternatives[n]. splitpoints[n] will only contain information about the corresponding alternative.s splitpoints if the alternative is a branch-expression.

    /api/mathjs

    Example inputs & outputs

    Request:

    {
      formula: <FPCore expression>
    }

    Response:

    {
      mathjs: <mathjs expression>
    }

    The mathjs endpoint allows the user to translate FPCore expressions into mathjs expressions.

    /api/cost

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        sample: [point ... ]
    }

    Response:

    {
        cost: cost
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 13120
    }

    Lower-Cost Example: (x+1)-(x)

    Request:

    {
        formula: <(FPCore (x) (- (+ x 1 ) x))>,
        sample: [ [[1], -1.4142135623730951] ]
    }

    Response:

    {
        cost: 320
    }

    The cost endpoint allows the user to request the evaluation of an expression's overall cost. Cost is calculated depending on the complexity of operations contained in the expression.

    Given an FPCore expression and a collection of points, returns the cost of the expression. The cost value returned is calculated internally by Herbie.

    The points should be of the same format as the points generated in the sample endpoint. Refer to /api/sample for more details.

    Note the sample points are not used in the cost calculation. The contents of the points do not matter as long as they are in the correct format as mentioned above.

    /api/translate

    Example inputs & outputs

    Request:

    {
        formula: <FPCore expression>,
        language: "language"
    }

    Response:

    {
        result: "translated expression"
    }

    Specific Example: sqrt(x+1)-sqrt(x)

    Request:

    {
        formula: <(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))>,
        language: "python"
    }

    Response:

    {
        result: "def expr(x): return math.sqrt((x + 1.0)) - math.sqrt(x)"
    }

    The translate endpoint allows users to translate FPCore expressions into equivalent expressions in various programming languages.

    Given an FPCore expression and a target language, this endpoint returns the translated expression in the specified language. The language parameter should be a string representing the desired programming language. The response includes the translated expression.

    Currently supported languages are: python, c, fortran, java, julia, matlab, wls, tex, and js.

    ⚠️ Beta endpoints

    The endpoints below are currently a work in progress.

    /api/localerror

    Forthcoming.

    /api/timeline/{job-id}

    Retrieves the timeline data for a given API call. You may find the job id in either the JSON response or in the headers of the HTTP response for of the endpoints in Herbie.

    The timeline is an exception to the others in that it uses a GET request. Below is a sample of what the request might look like. You may consult the /infra/testApi.mjs file for current examples of how to use this API.

    curl -X GET \
      http://127.0.0.1:8000/api/timeline/0b95161a77fc3e29376bbb013d96c2827e2a1cd7
          
    ================================================ FILE: www/doc/2.3/diagrams.html ================================================ Diagrams

    Diagrams

    This page contains systems diagrams for Herbie.

    System diagram of Herbie
    High-level system diagram of Herbie. It highlights Herbie's core architecture, key external libraries (egg and Rival), and user input/output. Basic flow: Herbie passes user input (specification, precondition, etc.) to a sampler which computes the exact output for uniformly random input points. Herbie uses these exact outputs to compute the accuracy of candidate programs. The mainloop (scheduler) then alternates between generate and prune phases, maintaining and improving a set of accurate expressions at each iteration. Once the generate-and-prune loop is complete, Herbie extracts either output expressions using regime inference, which combines multiple candidate programs using conditionals. The resulting programs are summarized in a report.
    ================================================ FILE: www/doc/2.3/docker.html ================================================ Herbie on Docker

    Installing with Docker

    Herbie is available through Docker, which is sort of like a virtual machine. This page describes how to install the official Docker image for Herbie.

    We recommend most users install Herbie from package or source. Herbie via Docker is only recommended if you already have Docker experience.

    Installing Herbie via Docker

    First, install Docker. Docker supports Windows, macOS, and Linux. Depending on how you install Docker, you may need to prefix the docker commands with sudo or run them as the administrative user.

    With Docker installed, you can run the Herbie shell with:

    docker run -it uwplse/herbie shell

    This will download the Herbie image and then run its shell tool.

    Running the web interface

    You can run the Herbie web server locally with

    docker run -it --rm -p 8000:80 uwplse/herbie

    and access the server at http://localhost:8000.

    (Herbie's Docker image binds to the container's port 80 by default; this command uses the -p 8000:80 option to expose that container port as the host's port 8000.)

    If you want to pass custom flags to the Herbie web server, make sure to also pass the --public and --port=80 flags to enable the Dockerized Herbie to talk to your computer. Make sure to access the server using HTTP, not HTTPS.

    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 below.

    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 --rm \
        -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 --rm \
        -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.

    Loading custom platforms

    If you'd like to write and use custom platforms with Herbie in Docker, you'll need to mount the platform directory as well:

    docker run -it --rm \
        -v platform-dir:/platform \
        -u $USER \
        uwplse/herbie shell \
        --platform /platform/platform.rkt

    You can use custom platforms for the web interface or in batch mode using a similar approach.

    Building the Docker image

    This section is primarily of interest the Herbie developers.

    Clone the repo and confirm that Herbie builds correctly with make install. Next, examine the Dockerfile and Makefile together. The Dockerfile should follow a process exactly like the Makefile, except a clean initial environment is assumed.

    The build is split into 2 or more stages to limit the size of the resulting image. Each stage consists of a FROM command and a series of further commands to build up the desired environment, and later stages can refer to earlier stages by name—for example, COPY --from=earlier-stage ... can copy files compiled in earlier images.

    Before building the official image, bump the version of Rust used for binary compilation and the version of Racket used in production, and adjusting paths to match the newest version of the repo.

    Once you are ready, run this from the repository root:

    docker build -t uwplse/herbie:test .

    This builds a new test image and tags it uwplse/herbie:test. You can run this image with:

    docker run -p 8000:80 -it uwplse/herbie:test

    The web demo should now be visible at http://localhost:8000.

    To open a shell in a running container for testing, first get the container ID with:

    docker ps

    Then open a root shell in that container with

    docker exec -it <CONTAINER ID> sh

    The code and egg-herbie binaries should be under /src.

    ================================================ FILE: www/doc/2.3/error.html ================================================ What is Error?

    What is Error?

    Herbie helps you identify and correct floating point error in your numeric programs. But what is floating point error, and how does Herbie measure it?

    The summary

    When Herbie reports a "percentage accuracy" number like 92.3%, it's usually best to think of that as the percentage of floating-point inputs where the expression is reasonably accurate.

    The impact of this error on your application will depend a lot on which 7.7% of inputs are inaccurate, and what kind of error that is. You can find this out using the accuracy graph in Herbie reports. You can also use preconditions to restrict the inputs Herbie is considering.

    Why rounding matters

    In mathematics, we work with real numbers, but on a computer, we typically use floating-point numbers. Because there are infinitely many real numbers, but only finitely many floating-point numbers, some real numbers can't be accurately represented. This means that every time you do an operation, the true result will be rounded to a representable one.

    Take an extreme example: the code 1e100 + 1, which increments a huge number in IEEE 754 double-precision floating-point. There's an exact real-number answer, a one followed by 99 zeros and then another 1, but the closest floating-point value is the same as 1e100.

    Errors like this can cause problems. In the example above, the answers differ by one part per googol, which is pretty small. But the error can grow. For example, since 1e100 + 1 rounds to the same value as 1e100, the larger computation

    (1e100 + 1) - 1e100

    returns 0 instead of the correct answer, 1. Now the difference is pretty stark, and can grow even bigger through later operations.

    Bits of error

    There are lots of ways to measure how much rounding error there is in a computation. Most people find the notions of absolute and relative error most intuitive, but Herbie internally uses a more complex notion called bits of error.

    The bits of error metric imagines you have a list of all of the possible floating-point numbers, from largest to smallest. In that list, compare the floating-point value you computed to the one closest to the true answer. If they are the same, that's called 0 bits of error; if they are one apart, that's called one bit of error; three apart is two bits of error; seven apart is three bits; and so on.

    In general, if the two floating-point values are n items apart, Herbie says they have log2(n + 1) bits of error. Values like 0 and -0 are treated as having 0 bits of error, and NaN is considered to have the maximum number of bits of error against non-NaN values. While there's all sorts of theoretical justifications, Herbie mainly uses this error metric because we've found it to give the best results.

    On a single input, the best way to interpret the "bits of error" metric is that it tells you roughly how many bits of the answer, starting at the end, are useless. With zero bits of error, you have the best answer possible. With four bits, that's still pretty good because it's four out of 64. But with 40 or 50 bits of error, you're getting less accuracy out of the computation than even a single-precision floating-point value. And it's even possible to have something like 58 or 60 bits of error, in which case even the sign and exponent bits (which in double-precision floating-point the the most significant 12 bits) are incorrect.

    Percentage accuracy

    Because different number representations have different numbers of bits, Herbie shows the percentage of bits that are accurate instead of the bits of error. With double-precision floats, for example, 75% accurate means 16 bits of error.

    Bits of error measures the error of a computation for some specific input. But usually you're interested in the error of a computation across many possible inputs. Herbie therefore averages the accuracy percentage across many randomly-sampled valid inputs.

    Typically, input points are either very accurate or not accurate at all. So when computing percentage accuracy, Herbie's averaging a lot of points with near-100% accuracy and a lot of points with near-0% accuracy. In that sense, you can think of percentage accuracy as measuring mostly what percentage of inputs are accurate. If Herbie says your computation is 75% accurate what it's really saying is that about a quarter of inputs produce usable outputs.

    Valid inputs

    When Herbie computes this average, it's over valid, uniformly distributed input points.

    Herbie considers an input valid if it is a floating-point value in the appropriate precision and its true, real-number output 1) exists; 2) satisfies the user's precondition; and 3) can be computed. Let's dive into each requirement.

    1. An output can fail to exist for an input if something like a division by zero or square root of a negative number happens even with exact, real-number computation. Then there's no exact answer to compare against and the point is considered invalid.
    2. An input can fail to satisfy the user's precondition, which are usually a range of inputs. For example, if the precondition is (< 1 x 2), then the input x = 3 is invalid.
    3. Finally, and most rarely, Herbie can fail to compute the output for a particular input. For example, the computation (/ (exp 1e9) (exp 1e9)), which divides two identical but gargantuan numbers, does have an exact real-number answer (one), but Herbie can't compute that exact answer because the intermediate values are too large. This input is also invalid, but you'll get a warning if this happens.

    Herbie's percentage accuracy metric only averages over valid points. This means that when you change your precondition, you change which points are valid, and that can change the percentage accuracy reported by Herbie. This is useful: if you've observed a floating-point error, you can tailor your precondition to focus on inputs near the one you've observed.

    Infinite outputs can be valid. For example, consider (exp x) for x = 1e100. The true real-number result is some very large finite number. That real number, whatever it is, rounds to infinity in double-precision floating point. Herbie thus considers this input valid. Since you might find this surprising, Herbie issues a warning if too many outputs are infinite.

    Sampling inputs

    When randomly sampling inputs, Herbie considers each valid input equally likely. Importantly, this does not mean that it uses a uniform distribution, because floating-point values themselves are not uniformly distributed.

    For example, there are as many floating-point values between 1 and 2 as there are between one and one half, because floating-point values use an exponential encoding. But that means that, in the interval [0.5, 2], Herbie will sample from the first third of that range twice as often as from the other two thirds.

    This can produce unintuitive results, especially for intervals that cross 0. For example, in the interval [0, 1], the second half of that interval (from one half to one) has a tiny proportion of the weight (in double-precision floating-point, about 0.1%). If Herbie can improve accuracy by a little bit between zero and one half, while dramatically reducing accuracy between one half and one, it will think that that's an accuracy improvement. For this reason, Herbie prompts you to add a minimum absolute value for ranges that cross zero. Even a trivial minimum absolute value, like 1e-10, can dramatically improve Herbie's results.

    Unfortunately, there's no way for Herbie to intuit exactly what you mean, so understanding this error distribution is essential to understanding Herbie's outputs. For example, if Herbie reports that accuracy improved from 75% to 76%, it's essential to know: is the improvement happening on inputs between one half and one, or between 1e-100 and 2e-100? To answer this question, it's important to look over the reports and graphs generated by Herbie.

    ================================================ FILE: www/doc/2.3/faq.html ================================================ Herbie Warnings and Errors

    Common Warnings and Errors

    Herbie issues a variety of warnings and errors when something unexpected happens during compilation.

    Common warnings

    N% of points produce a very large (infinite) output.

    Sometimes, an input to your expression produces an output so large that it's best represented by a floating-point infinity. For example, (exp 1000) is over 10434, so it's much larger than the largest floating-point value. Herbie raises this warning when too many inputs (more than 20% of them) are this large. When you see this warning, you should usually set a more restrictive precondition.

    Could not determine a ground truth

    Herbie raises this warning when some inputs require more than 10,000 bits to compute an exact ground truth. For example, to compute (/ (exp x) (exp x)) for x = 1e100, absurdly large numbers would be required. Herbie discards these inputs and raises this warning. When you see this warning, you should usually set a more restrictive precondition.

    Could not uniquely print val

    Herbie will raise this warning when it needs more than 10,000 bits to produce a unique decimal representation for a given value. This is likely the result of a bug in a custom platform, likely in a representation definition. The platform needs to be fixed.

    Unused variable var

    The input FPCore contains a variable that is not used in the body expression. You should remove the unused variable.

    Unusual variable var

    The input expression contains a variable that is named similar to some named constant, like e instead of E. You should use a different variable name.

    Unsoundness detected in the egraph

    Herbie raises this warning when its algebraic rewriting phase fails. This indicates a bug in Herbie itself and should be reported to the developers.

    Common errors

    Invalid syntax

    This error means you mis-formatted Herbie's input. Common errors include misspelled function names and parenthesized expressions that should not be parenthesized. For example, in (- (exp (x)) 1), the expression x is a variable so shouldn't be parenthesized; (- (exp x) 1) is correct. Follow the input format more carefully.

    Cannot sample enough valid points

    This error occurs when Herbie is unable to find enough valid points. For example, the expression (acos (+ 1000 x)) is invalid unless (<= -1001 x -999), a rather narrow range. You can specify a more restrictive precondition or pass a larger value for the --num-analysis flag.

    No valid values

    This error indicates that your input has no valid inputs, usually due to an overly restriction precondition. For example, the precondition (< 3 x 2) excludes all inputs. You should fix either the precondition or the input program.

    ================================================ FILE: www/doc/2.3/input.html ================================================ Herbie Input Format

    The Input Format

    Herbie uses the FPCore format to specify an input program, and has extensive options for precisely describing its context.

    Math format

    The Herbie web shell takes input in standard math syntax. More specifically, it uses a subset of the math.js syntax. The web shell automatically checks for syntax errors, and provides a graphical user interface for specifying the input domain. The web shell converts the mathematical expression and input ranges into FPCore before sending it to Herbie.

    FPCore format

    Herbie's command-line and batch-mode tools use FPCore format to describe mathematical expressions. FPCore looks like this:

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

    Each input is a variable name, like x, used in the expression. Properties are used to specify additional information about the expression's context.

    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))))

    The semicolon (;) character introduces a line comment. We recommend the .fpcore file extension for FPCore files.

    Supported functions

    FPCore expressions can use any of the following functions:

    +, -, *, /, fabs
    The usual arithmetic functions
    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. Use - for both subtraction and negation.

    However, how Herbie evaluates these functions, their cost, and what additional functions are available depends on the platform you select.

    Conditionals

    FPCore uses if for conditional expressions:

    (if cond if-true if-false)

    The conditional cond may use:

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

    The comparison operators support chained comparisons with more than two arguments; for example (< 1 x 10) means 1 < x < 10.

    Intermediate variables

    Intermediate variables can be defined using let and let*:

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

    In both let and let*, each variable is bound to its value and can be used in the body. The difference between let and let* is what order the values are evaluated in:

    let expressions
    In a let expression, all the values are evaluated in parallel, before they are bound to their variables. This means that later values can't refer to earlier variables in the same let block.
    let* expressions
    A let* block looks the same as a let block, except the values are evaluated one at a time, and later values can refer to earlier variables.

    Unless you have a lot of Lisp experience, you'll probably find let* more intuitive.

    Internally, Herbie treats intermediate values only as a notational convenience, and inlines their values before improving the formula's accuracy. Using intermediate variables will therefore not produce a more accurate result or help Herbie run faster.

    Specifications

    In some cases, your input program is an approximation of some more complex mathematical expression. The :spec (for “specification”) lets you specify the more complex ideal case. Herbie will then try to modify the input program to make it more accurately evaluate the specification.

    For example, suppose you want to evaluate sin(1/x) via a series expansion. Write:

    (FPCore (x)
      :spec (sin (/ 1 x))
      (+ (/ 1 x) (/ 1 (* 6 (pow x 3)))))

    Herbie will use the :spec expression to evaluate error, but use body expression as a starting-point for finding more accurate expressions.

    Preconditions

    By default, the arguments to formulas are assumed to be arbitrarily large or small floating-point numbers. However, in most programs a smaller range of argument values is possible. The :pre property (for “precondition”) describes this smaller range.

    Preconditions use comparison and boolean operators, just like conditional statements:

    (FPCore (x) :pre (< 1 x 10) (/ 1 (- x 1)))

    Herbie is particularly efficient when when the precondition is an and of ranges for each variable, but more complex preconditions also work.

    Precisions

    Herbie supports both single- and double-precision values; you can specify the precision with the :precision property:

    binary32
    Single-precision IEEE-754 floating point
    binary64
    Double-precision IEEE-754 floating point

    Platforms can also add additional precisions.

    Miscellaneous Input Properties

    A name can be provided before the argument list to name an FPCore. That FPCore can then be called in other, later FPCores.

    Herbie uses the :name property to name FPCores in its UI. Its value ought to be a string.

    Herbie allows :alt properties to specify additional "developer targets"; these might be other alternatives you've tried that you want to compare against.

    Herbie's benchmark suite also uses properties for continuous integration, but these are not officially supported and their use is discouraged.

    ================================================ FILE: www/doc/2.3/installing.html ================================================ Installing Herbie

    Installing Herbie

    Herbie supports Linux, macOS, and Windows. To start, install Racket. Then install Herbie, either from a package, from source, or via a Docker image.

    Installing Racket

    Install Racket either using the official installer or distro-provided packages. Versions as old as 8.10 are supported, but more recent versions are faster.

    On Linux, we recommend against installing Racket via Snap. If you must install Racket from Snap, install from source and store that source directory in your home directory or another allow-listed directory.

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

    racket
    Welcome to Racket v8.17 [cs].
    > (exit)

    Installing Herbie from a package

    Herbie can be installed as a binary package on x86 Windows and Linux and on x86 or ARM macOS. If you are on a more obscure platform, please install from source instead.

    After installing Racket, install Herbie from a package with:

    raco pkg install --auto herbie

    Then check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.2

    If you'd like, you can run Herbie with the herbie command by adding the following directory to your PATH (example paths for Racket 8.17):

    • On Windows, AppData\Roaming\Racket\8.17\bin in your user folder.
    • On macOS, Library/Racket/8.17/bin in your home folder.
    • On Linux, .local/share/racket/8.17/bin in your home directory.

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

    Installing Herbie from source

    Installing Herbie from source is best if you are a Herbie developer, or if you use a more obscure hardware/OS combination. The instructions assume a standard Unix userland; on Windows, you may have to install tools like Make separately, or use WSL.

    Install Racket as described above. Then install Rust 1.60.0 or later using rustup or some other means.

    Once Racket and Rust are installed, download the Herbie source from GitHub with:

    git clone https://github.com/herbie-fp/herbie

    Change to the herbie directory; you should see a README.md file, a directory named src, a directory named bench/, and a few other files and directories. Install Herbie with:

    make install

    This command installs Herbie and its dependencies and compiles it for faster startup. Check that Herbie works by running:

    racket -l herbie -- --version
    Herbie 2.2

    You can add herbie to your path, as described in the package-install instructions. Once Herbie is installed and working correctly, check out the tutorial.

    Installing Herbie with Docker

    Docker is a container manager, which is sort of like a virtual machine. We do not recommend using Herbie in Docker without prior Docker experience.

    The Docker documentation describes how to install and run our uwplse/herbie image.

    ================================================ FILE: www/doc/2.3/options.html ================================================ Herbie Command-line Options

    Command-line Options

    The herbie command has subcommands and options that influence both its user interface and the quality of its output.

    Herbie commands

    Herbie provides several subcommands, which offer interactive and batch modes for both the command line and the web interface:

    racket -l herbie web
    Use Herbie through your browser using a local server. This server can also be used from Odyssey.
    racket -l herbie shell
    Use Herbie via a command-line shell. Enter an FPCore expression and Herbie prints faster and more accurate alternatives.
    racket -l herbie improve input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a single file in FPCore format.
    racket -l herbie report input output
    Run Herbie on the expressions in the file or directory input. The results are written to output, a directory of HTML reports. Viewing these requires a web server.

    We recommend the web subcommand for interactive use with detailed reports that include graphs of error versus input values and plots comparing cost and accuracy. This can help you understand whether Herbie's improvements matter for your use case.

    Use herbie subcommand --help to view available command-line options for a subcommand. This command also shows undocumented subcommands not listed on this page.

    General options

    General options apply to all subcommands and are passed after the subcommand name but before other arguments, like this:

    racket -l herbie report --timeout 60 in.fpcore out/

    Options must go before subcommand arguments like input and output paths.

    --platform P
    Herbie's backend platform, which affects the operations available to Herbie, their accuracies, and their costs. The platform name is either one of the built-in platforms, or the path to a custom platform. In general, it's best to select the platform that most closely matches the programming language and hardware where you will be running floating-point code.
    --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 (not including the latter). Two runs of Herbie with the same seed should produce identical results. By default, a random seed is chosen.
    --timeout T
    The timeout to use per-input, in seconds. A fractional number of seconds can be given. By default, no timeout is used.
    --threads N
    Enables multi-threaded operation. By default, no threads are used. The argument is the number of threads to use, or yes to use all of the hardware threads.
    --num-points N
    The number of input points Herbie uses to evaluate candidate expressions. The default, 256, is a good balance for most programs. Increasing this option, say to 512 or 1024, will slow Herbie down but may make its results more consistent.
    --num-iters N
    The number of attempts Herbie makes to improve accuracy. The default, 4, suffices for most programs, and more iterations are rarely beneficial. But increase this option, say to 6, can sometimes lead to more accurate or faster results.
    --num-analysis N
    The number of input subdivisions to use when searching for valid input points. The default is 12. Increasing this option will slow Herbie down, but may fix a "Cannot sample enough valid points" error.
    --num-enodes N
    The number of equivalence graph nodes to use when doing algebraic reasoning. The default is 4000. Increasing this option will slow Herbie down, but may improve results slightly.

    Web shell options

    The web tool runs Herbie and connects to it from your browser. It has options to control the underlying web server.

    --port N
    The port the Herbie server runs on. The default port is 8000.
    --save-session dir
    Save all the reports to this directory. The directory also caches previously-computed expressions.
    --log file
    Write an access log to this file, formatted like an Apache log. This log does not contain crash tracebacks.
    --quiet
    By default, but not when this option is set, a browser is automatically started to show the Herbie page. This option also shrinks the text printed on start up.
    --no-browser
    This flag disables the default behavior of opening the Herbie page in your default browser.
    --public
    When set, users on other computers can connect to the demo and use it. (In other words, the server listens on 0.0.0.0.) Essential when Herbie is run from Docker.

    Rulesets

    Herbie uses rewrite rules to change programs and improve accuracy. The --disable rules:group and --enable rules:group options turn rule sets on and off. In general, turning a ruleset on makes Herbie produce more accurate programs.

    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

    Search options

    These options enable or disable transformations that Herbie uses to find candidate programs. We recommend sticking to the defaults.

    Each option can be turned off with the -o or --disable command-line flag and on with +o or --enable. Turning an option off typically results in less-accurate results, while turning a option on typically results in more confusing output expressions.

    setup:search
    This option, on by default, uses interval subdivision search to help compute ground truth for complicated expressions. If turned off, Herbie will be slightly faster, but may hit the "Cannot sample enough valid points" error more often. Instead of turning this option off, try adjusting the --num-analysis flag.
    setup:preprocess
    This option, on by default, uses identities of the input expression (like even or symmetric expressions) to perform rewriting more effectively. If turned off, Herbie will skip preprocessing and work directly with the original expression. This can be faster but may produce less accurate results for expressions with exploitable symmetries.
    generate:rr
    This option, on by default, uses algebraic rewriting to generate candidate programs. This is Herbie's primary method of improving accuracy, and we do not recommend turning off this option.
    generate:taylor
    This option, on by default, uses series expansion to generate candidate programs. If turned off, Herbie will not use series expansion, which may help accuracy in some ranges while leaving Herbie unable to solve certain under- and overflow issues.
    generate:evaluate
    This option, on by default, uses arbitrary-precision arithmetic to generate candidate programs, specifically by exactly computing some constant expressions. If turned off, these exact computations won't be performed and Herbie won't be able to improve accuracy in those cases.
    generate:proofs
    This option, on by default, generates step-by-step derivations for HTML reports. If turned off, the step-by-step derivations will be absent, and Herbie will be slightly faster.
    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). Instead of turning this option off, consider increasing your platform's if cost to discourage branches.
    reduce:binary-search
    This option, on by default, uses binary search to refine the values used in inferred branches. If turned off, different runs of Herbie will be less consistent, and accuracy near branches will suffer.
    reduce:branch-expressions
    This option, on by default, allows Herbie to branch on expressions, not just variables. This slows Herbie down, particularly for large programs. If turned off, Herbie will only try to branch on variables.
    ================================================ FILE: www/doc/2.3/platforms.html ================================================ Herbie Platforms

    Writing a Herbie Platform

    Platforms define Herbie compilation targets. A platform might be specific to a programming language, to a math library, or to a hardware ISA. Writing a custom platforms can help Herbie produce faster and more accurate programs.

    Platform Concepts

    Herbie operates on mathematical specifications and floating-point expressions.

    Specifications are built from rational numbers, variables, and functions like +, sin, <, and if. A specification has a type, which is either real or bool.

    Types, functions, and specifications have floating-point analogs.

    Representations are the floating-point analogs of types, and Herbie comes with three built in: <binary32> and <binary64> are reprentations of real and correspond to single- and double-precision IEEE-754 arithmetic. There's also a <bool> representation for booleans. It's possible to define new representations, described on another page.

    Operations are the floating-point analog of functions and represent the actual floating-point operation the compilation target can perform. There are typically several operations for each supported function; for example, in the C standard library provides cosf and cos, both of which correspond to the cos function but for different representations (<binary32> and <binary64>).

    Expressions are the floating-point analog of specifications and represent target-specific floating-point computations. Platforms, to put it simply, just define the set of representations and operations that expressions are allowed to use. Herbie then searches for a fast and accurate expression that corresponds to the user's input specification.

    Each representation, operation, and thus expression has a cost, which is a non-negative real number. Generally, the cost of a representation is the time it takes to read a value of that representation from memory and the cost of an operation is the time it takes to execute that operation. However, in principle, platforms can use cost to represent another metric like area or latency. Only relative costs matter. If you're not sure, just putting "1" for all the costs is not a bad place to start.

    Defining a Platform

    A platform definition is a text file that starts with:

    #lang herbie/platform

    Herbie can then be informed to use this platform by passing the --platform path/to/file command-line argument.

    The file contains Racket code. That code can define the platform's representations and operations using define-representation and define-operation. It can also define variables and helper functions, import external packages, or anything else Racket can do. If necessary, it can define new representations.

    Herbie's built-in platforms are good example platforms to study or modify. If you use them as an example, you'll need to change the #lang line at the top of the file to be herbie/platforms; the built-in platforms are different because they are built in to Herbie and can't assume Herbie is installed.

    Defining Representations

    The typical platform starts by defining the representations it uses and their costs with define-representation:

    (define-representation <bool> #:cost 1)
    (define-representation <binary64> #:cost 1.5)

    This cost is the cost for reading a variable or literal of that representation. Note that platforms must explicitly define a cost for the <bool> representation if it uses booleans. If the platform forgets to define a representation that it uses, Herbie will produce an error when loading the platform.

    If the same cost is used repeatedly, it can be convenient to define a variable:

    (define cost 1)
    (define-representation <bool> #:cost cost)

    After defining the representations it uses, a platform then defines all the operations it supports.

    Defining Operations

    An operation is defined by four fields:

    • A signature, which gives the operation's name and its input and output representations.
    • A specification for the operation's mathematical behavior.
    • An implementation that computes the operation's output given concrete inputs.
    • A cost for using the operation in an expression.

    The define-operation construct requires exactly these four fields:

    (define-operation (recip [x <binary32>]) <binary32>
      #:spec (/ 1 x) #:impl (lambda (x) ...) #:cost 3)

    The first line gives the operation's signature: it is named recip, it has one single-precision input x, and it outputs a single-precision float.

    The #:spec field gives this operation's specification as (/ 1 x), one divided by x. In other words, this operation computes a number's reciprocal.

    The #:impl field gives this operation's implementation, as a Racket function (defined with lambda). An operation's implementation is a Racket function with as many arguments as the operation. It is called with concrete inputs in the corresponding input representations, and must return an output in the output representation. It can be defined using a lambda, a define block, or any other function-defining Racket construct.

    When an implementation function is called, <binary64> and <binary32> arguments are passed as Racket flonums, while <bool> arguments are passed as Racket booleans. Single-precision numbers aren't a separate type in Racket. instead, you create them from double-precision floats by calling flsingle.

    For this example, to compute a 32-bit reciprocal for a 32-bit input, one could use (flsingle (/ 1.0 x)) for the body of the lambda.

    The #:cost field gives this operation's cost, 3.

    Defining Multiple Operations

    Realistic platform usually have a lot of similar operations: addition, subtraction, multiplication, and division, or sine, cosine, tangent, and so on. The define-operations construct defines multiple operations at a time, as long as they have the same input and output representations:

    (define-operations ([x <binary64>] [y <binary64>]) <binary64>
        [+ #:spec (+ x y) #:impl + #:cost 0.200]
        [- #:spec (- x y) #:impl - #:cost 0.200]
        [* #:spec (* x y) #:impl * #:cost 0.250]
        [/ #:spec (/ x y) #:impl / #:cost 0.350])

    This block defines four functions, each with their own name, specification, implementation, and cost. Note that in this case the #:impl column refers to the Racket functions +, -, *, and /.

    Common Kinds of Operations

    This section lists common kinds of operations and notes things you should keep in mind when defining them.

    Math Library Functions

    Most operating systems have a standard libm library that provides elementary functions like cos. You can use Herbie's from-libm helper to load implementations from libm:

    (define-operation (fabs.f32 [x <binary32>]) <binary32>
      #:spec (fabs x) #:impl (from-libm 'fabsf) #:cost 0.125)

    The from-libm helper uses dynamic linking to load libm, extracts the symbol passed to from-libm, and then uses the operation's signature to call into the dynamically-linked library from Racket. Be sure to pass the correct symbol name; for single-precision functions, add the "f" suffix.

    For other libraries, open them with ffi-lib and use from-ffi, which generalizes from-libm. For example, to load functions from the GNU Scientific Library:

    (require ffi/unsafe)
    (define libgsl (ffi-lib "gsl"))
    (define-operation (cos.gsl [x <binary64>]) <binary64>
      #:spec (cos x) #:impl (from-ffi libgsl 'gsl_sf_cos) #:cost 0.125)

    Numeric Variations

    Some platforms provide numeric variations like log1p or cosd for common functions. You can define operations for them using complex specifications:

    (define-operation (cosd [x <binary64>]) <binary64>
      #:spec (cos (* x (/ (PI) 180))) #:impl (lambda (x) ...) #:cost 4)

    The #:spec in this example explains to Herbie that cosd is the cosine of x in degrees. Herbie will then use cosd when that improves accuracy.

    Other common numeric variations include fma, log1p, expm1, and hypot. If they're available on your target, we strongly recommend defining operations for them; they often improve accuracy by quite a bit!

    Conversions

    A conversion or cast operation uses mixed representations:

    (define-operation (64->32 [x <binary64>]) <binary32>
      #:spec x #:impl flsingle #:cost 1)

    This operation has a 64-bit input and a 32-bit output. Its specification is just x, which means it doesn't actually do anything mathematically. The implementation is the standard Racket flsingle function (which converts from double- to single-precision) and it has a cost of 1.

    Herbie will use this conversion operation for casting between the two types.

    Comparisons

    Comparison operations return <bool>:

    (define-operations ([x <binary64>] [y <binary64>]) <bool>
      [==.f64 #:spec (== x y) #:impl =          #:cost 1]
      [!=.f64 #:spec (!= x y) #:impl (negate =) #:cost 1]
      [<.f64  #:spec (< x y)  #:impl <          #:cost 1]
      [>.f64  #:spec (> x y)  #:impl >          #:cost 1]
      [<=.f64 #:spec (<= x y) #:impl <=         #:cost 1]
      [>=.f64 #:spec (>= x y) #:impl >=         #:cost 1])

    Here, negate is a Racket function that negates a comparison function.

    A platform only needs to provide the comparison functions available on the target. However, Herbie's "regimes" pass uses the <= operation, so it's best to provide one if one is available.

    A platform that uses both representation needs to define separate <binary32> and <binary64> comparison operations. They can have different costs.

    Conditionals

    Conditionals can be defined as operations with a boolean argument:

    (define-operation (if-f64 [c <bool>] [t <binary64>] [f <binary64>]) <binary64>
      #:spec (if c t f) #:impl if-impl
      #:cost (if-cost 14) )

    Here if-impl is a Herbie-provided Racket function that wraps a standard if statement, while the if inside the #:spec is how specifications refer to mathematical conditional expressions.

    Conditional operations usually pass a procedure to #:cost. The helper if-cost returns a procedure that receives the costs of the arguments and combines them into a total cost. It computes the cost of evaluating the condition plus the larger of the two branch costs. In this example we add a constant cost of 14 to that value.

    A platform needs to define both <binary32> and <binary64> conditional operations if it uses both representations. They can have different costs. There's typically no need to define conditional operations for <bool> as Herbie does not rewrite boolean expressions.

    Constants

    Mathematical constants like E and PI are considered operations in Herbie; they just have no inputs. For example, to define a 64-bit inverse-π constant, write:

    (define-operation (INVPI) <binary64>
      #:spec (/ 1 (PI)) #:impl (lambda () ...) #:fpcore PI #:cost 0.5)

    Note the parentheses in various fields. The name INVPI has parentheses around it like all operation signatures; it just doesn't have any arguments after the name. In the #:spec, the PI constant is also wrapped in parentheses because it is also treated as a zero-argument function. And in the #:impl, the implementation is given by a Racket function of no arguments. You can also use the Racket const function to construct these no-argument functions.

    But in the #:fpcore field the PI value doesn't use parentheses, because FPCore treats constants as constants, not zero-argument functions.

    Constants can be defined in any precision. If you want the same constant to be available in multiple precisions, you have to define multiple constant operations.

    Negation

    Herbie's specification language has a negation function, and it's usually a good idea to define a negation operation if your target has one. Define it like this:

    (define-operation (neg.f32 [x <binary64>]) <binary64>
        #:spec (neg x) #:impl - #:fpcore (- x) #:cost 0.125)

    Here, in the #:spec, (neg x) refers to the negation function in Herbie's specification language, while the - symbol after #:impl refers to Racket's subtraction function, which also acts as a negation function.

    There is also an #:fpcore field. This field tells Herbie how to represent this operation in FPCore (for user input and output). The default #:fpcore is the operation's #:spec but there are a few functions (like negation) where Herbie's specification language uses a different syntax from FPCore and #:fpcore needs to be specified manually.

    Precision-specific Variations

    If a platform supports both <binary32> and <binary64>, they often support similar operations:

    (define-operations ([x <binary32>]) <binary32>
        #:fpcore (! :precision binary32 _)
        [fabs.f32 #:spec (fabs x) #:impl (from-libm 'fabsf) #:cost 0.125]
        [sin.f32  #:spec (sin x)  #:impl (from-libm 'sinf)  #:cost 4.250]
        ...)
      (define-operations ([x <binary64>]) <binary64>
        #:fpcore (! :precision binary64 _)
        [fabs.f64 #:spec (fabs x) #:impl (from-libm 'fabs)  #:cost 0.125]
        [sin.f64  #:spec (sin x)  #:impl (from-libm 'sin)   #:cost 4.200]
        ...)

    Here, two define-operations blocks define two sets of functions with different signatures but identical specifications. To disambiguate these functions in FPCore output, the #:fpcore argument to define-operations defines different FPCore properties for each set of operations. In that argument, the underscore is replaced by each operation's #:spec.

    Hard-to-emulate Operations

    Sometimes a platform will offer an operation that's difficult to implement accurately. In this case, Herbie's from-rival helper can provide a slow but correctly-rounded implementation:

    (define-opertion (dotprod [a <binary64>] [b <binary64>]
                                [c <binary64>] [d <binary64>]) <binary64>
      #:spec (+ (* a b) (* c d)) #:impl (from-rival) #:cost 1.25)

    The from-rival helper uses the MPFR library to evaluate the operation's specification. Compilation is usually much slower than with a native floating-point implementation, but for unusual operations that are difficult to implement otherwise, it can still allow compilation to proceed. There is an optional #:cache? argument to from-rival, on by default. The cache makes Herbie much faster but uses a lot of memory.

    Note that from-rival implementations are always "correctly-rounded", meaning as accurate as possible. Most targets don't actually offer correctly-rounded operations, which can mean that Herbie's outputs won't be as accurate as Herbie assumes. It's always better to execute the actual operation on the actual target if possible, so as to precisely emulate its actual behavior.

    ================================================ FILE: www/doc/2.3/plugins.html ================================================ Herbie Other APIs

    Other Herbie APIs

    Herbie platforms are the main way to customize Herbie's behavior. The typical platform just defines the set of representations and operations that available to Herbie when compiling. However, platform files can technically contain arbitrary Racket code, and thus can call other Herbie APIs, including importing external libraries, defining new representations, and so on. This page documents such APIs. Note that a level of comfort with Racket is assumed.

    Please note that all of the APIs on this page are considered unstable and may change version to version. If you run into issues, please file a bug.

    Defining representations

    Representations what Herbie calls different number formats. They play a central role in platforms. Concretely, a representation is a set of Racket values that represent both real numbers and bit patterns.

    Specifically, a representation value needs to be convertible to Racket bigfloat values (which are basically MPFR floats) and also to ordinals, meaning integers between -2w−1 and 2w−1−1 for some bit width w.

    Create representations with make-representation:

    (make-representation
    #:name name
    #:total-bits width
    #:bf->repr bf->repr
    #:repr->bf repr->bf
    #:ordinal->repr ordinal->repr
    #:repr->ordinal repr->ordinal
    #:special-value? special?)

    The #:name should be either a symbol, or a list containing symbols and integers. The #:total-bits value should be a positive integer. The #:total-bits parameter determines the total ordinal range your format takes up, not just its significand range, so for example for double-precision floats you need a #:total-bits of 64.

    The #:bf->repr and #:repr->bf values should be that convert between representation values and Racket bigfloats. The repr->bf function should use as large a bigfloat precision as needed to exactly represent the value, while the bf->repr function should round as per the current value of the bf-rounding-mode parameter.

    All non-NaN bigfloat values should result in non-NaN representation values. For example, (bf->repr (bf "1e1000000")) should yield the largest real value in the representation. Infinite values, as in (bf->repr +inf.bf), should be interpreted as really large real values, not as infinite values. For example, the posit format has an "infinite" value, but it behaves more like a NaN, so converting bigfloat infinity to a posit should yield its largest real value instead.

    The #:ordinal->repr and #:repr->ordinal functions represent ordinals as Racket integers between -2width−1 (inclusive) and 2width−1 (exclusive). Ordinals must be in real-number order; that is, if (repr->bf x) is less than (repr->bf y), then (repr->ordinal x) should be less than (repr->ordinal y).

    The #:special function should return true for NaN values (or whatever your representation calls values that don't represent any real number) and false for all other values. Special values can be anywhere in the ordinal range, and you can have as many or as few of them as you want.

    make-representation returns a representation object, which you can then use in define-representation and define-operation.

    Defining Generators

    Generators are helper functions for generating implementations in define-operation. For example, from-ffi is a generator.

    To define a generator, use define-generator:

    (define-generator ((from-foo args ...) spec ctx)
    body ...)

    Here, from-foo is the name of your generator, and args are any additional arguments the generator takes. For example, from-libm takes one argument, the symbol name, while from-ffi takes an open library and a symbol name.

    Then, inside the body, you can use those arguments as well as spec and ctx, to construct an actual implementation function.

    The specification spec is a Racket tree made up of lists and symbols and numbers.

    The signature ctx is "context" object; you can access its context-repr to get the operation's output representation, its context-vars to access its variable names (as a list of symbols), and its context-var-reprs to access its input representations (as a parallel list of representations). The context-lookup function can be used to look up a input argument's representation by name.

    ================================================ FILE: www/doc/2.3/release-notes.html ================================================ Herbie 2.2 Release Notes

    Herbie 2.2 Release Notes

    The Herbie developers are excited to announce Herbie 2.2! This release focuses extensible compilation targets using platforms.

    What is Herbie? Herbie compiles mathematical expressions to fast and accurate floating point programs, avoiding the bugs, errors, and surprises of floating point arithmetic. Visit the main page to learn more.

    Releasing the Platforms API

    (define-operation (cosd [x <binary64>]) <binary64>
              #:spec (cos (* x (/ (PI) 180)))
              #:impl (from-rival)
              #:cost 12.5)
    Herbie 2.2's platforms allow you to define custom compilation targets, including novel hardware (CPUs, GPUs, FPGAs, TPUs), programming languages (Julia, Matlab, Fortran), or software libraries (Numpy, Eigen, cuBLAS). Platforms can define new number formats, new floating-point operations on those formats, and new cost models for those operations. Herbie will automatically optimize its results for your platform.

    Last year, Herbie 2.1 included the beginnings of platforms, Herbie's API for multiple backends. We've simplified and improved this API, and are now ready to release it.

    In short, platforms allow you define the functions Herbie is allowed to use when compiling floating-point programs. A platform can expose Python's fsum, Julia's cosd, or AVX's rcpps to Herbie, which can then use those functions to produce even faster and more accurate output. Our recent publications demonstrate dramatic accuracy and performance improvements using platforms.

    Platforms are a large change, and we invite users to try it out. The platforms documentation explains how to select different built-in platforms, or even how to write your own.

    Besides the new feature, platforms allow us to deprecate a variety of now-superseded features:

    • The default platform now uses a realistic, auto-tuned cost model which should lead to faster code in practice.
    • The Herbie 2.0 and 2.1 cost models are replaced by the herbie20 platform.
    • The --no-pareto flag is replaced by the herbie10 platform.
    • Plugins are now replaced by ordinary Racket libraries imported directly by plugins.
    • Preprocessing is now replaced by standard platform-provided functions such as fmin, fmax, fabs, and copysign.

    Restructuring the Main Loop

    The new Herbie main loop.
              The simplify step and patch table have been removed,
              while the compute phase has been added.
    The Herbie main loop is dramatically simplified, with no separate simplify or localize phase and a new compute phase that aid with hard-to-compute constants.

    We've overhauled the Herbie main loop, which is the high-level algorithm that invokes different Herbie sub-systems to generate and filter different candidate programs. This overhaul both introduces new systems, which should make Herbie more capable, and reduce duplicative work, dramatically speeding up Herbie. Specifically:

    • A new compute phase was added, which replaces variable-free subexpressions with constants. The constants are computed using high-precision arithmetic and are guaranteed to be exact.
    • The simplification phase was removed. This phase mostly duplicated work already done by the rewrite phase, so removing it had very little impact on performance and accuracy of generated code, but reduced Herbie runtime significantly.
    • The localize phase was also removed. This phase existed to reduce duplicate work in other phases; batches deduplicate the work automatically so localize is no longer needed. Removing this phase also significantly sped up Herbie.
    • Minor phases like initial and final simplify were removed as well. Most importantly, this allowed replacing the existing, complex "accelerator" API (never fully released) and replace it with the much simpler "platform" API.
    • Herbie more correctly tracks Taylor approximations inside Herbie, and will now avoid making "approximations to approximations" leading to runaway error in rare cases.

    Flattening Expressions to Batches

    %0 = x
    %1 = (literal 1)
    %2 = (+ %0 %1)
    %3 = (* %2 %2)
    %4 = (- %3 %1)
    %5 = (neg %2)
    %6 = (* %5 %5)
    %7 = (- %6 %1))
    An example of a batch, containing two expressions (and their subexpressions): (x + 1)2 - 1 and (-(x + 1))2 - 1. Shared subexpressions are only represented once in the batch, and are only processed once by Herbie.

    We have also changed Herbie's main data structure for representing programs. Until now, Herbie represented programs via their abstract syntax tree. While simple and effective, this representation lead to processing identical subexpressions repeatedly. This was especially problematic for larger programs, which can sometimes have the same subexpression appear exponentially many times.

    The batch data structure instead uses a flat array with back-references to represent programs, which means that duplicate subexpressions appear and are processed just once. This lead to significant speedups throughout Herbie:

    • Batches automatically deduplicates identical candidate programs, which occur when multiple Herbie phases produce the same output. There were a lot more of these than we thought; in some cases as much as 25× deduplication occurs.
    • The series phase, now uses batches internally, allowing Herbie to consider many more series expansions.
    • Herbie's FFI layer for interacting with the egg library now leverages batches to reduce time spent copying data in and out of egg.
    • In some cases, one batch is shared across multiple phases (like evaluation and pruning), reducing duplication even further.
    • The derivations phase was dramatically sped up by caching proof objects across multiple candidate programs. Additionally, a debugging pass called "soundiness" was removed entirely, since the problems it was built to debug no longer occur. Thanks to both changes, derivations take almost no time at all any more.

    Sister Projects

    The Odyssey numerics workbench is releasing version 1.2 today, featuring a new "local error" component and various design tweaks and improvements. Odyssey can be tried from its online demo or installed locally from the VS Code Marketplace.

    The Rival real-arithmetic package is releasing version 2.2 today, including various tweaks, optimizations, and improvements. Most significantly, a new "hints" feature significantly speeds up sampling expressions that use fmin, fmax, and if.

    Other improvements

    • Herbie now offers a basic HTTP API, including both synchronous and asynchronous jobs and access to various internal Herbie features. This HTTP API is a work in progress and is primarily built to support Odyssey. However, our work on the API has also enabled the standard Herbie web interface to support threads, which should make Herbie faster in a lot of common use cases.
    • Small quality-of-life changes have been made to the Herbie reports, including hiding duplicate alternatives, updating various dependencies, and generating the reports more quickly.
    • Optional, off-by-default support for the egglog rewriting engine has been added. We are excited about growth and competition among rewrite engines and hope to continue driving forward this research area.
    • Fixing a variety of small bugs, such as nondeterminism due to an incorrect type signature for fma and new rewrite rules to unlock more accurate results.
    • Many general-purpose cleanups, including removing a lot of now-unnecessary nightly infrastructure, adoption of a new formatting guideline, reorganization of the source code, and new debug infrastructure.

    Try it out!

    We want Herbie to be 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 or contribute.


    If you find Herbie useful, let us know!

    ================================================ FILE: www/doc/2.3/report.html ================================================ Herbie reports

    Herbie reports

    When used in the browser, Herbie generates HTML reports full of information about the accuracy and relative speed of the initial and alternative expressions.

    The top of the report page has a right-hand menu bar with additional links. “Metrics” give you detailed internal information about Herbie, while “Report”, if present, takes you back to the full Herbie report.

    Below the menu lies a brief summary of the results. Herbie can produce multiple alternatives to the initial program, and this summary gives their basic statistics.

    Summary numbers from a Herbie report.
    Percentage Accurate
    The percentage accuracy of the initial program and what Herbie thinks is its most accurate alternative.
    Time
    The time it took Herbie to generate all of the alternatives.
    Alternatives
    The number of alternatives found.
    Speedup
    The speed, relative to the initial program, of the fastest alternative that improves accuracy.

    Specification

    Next, the specification that you gave to Herbie. This section is closed by default. Typically, the specification is also the initial program, but in some cases, like if the :spec property is used, they can differ. The specification also includes any preconditions given to Herbie.

    You can use the drop-down in the top left to display the specification in an alternative syntax.

    Local Percentage Accuracy graph

    Next, the Local Percentage Accuracy graph compares the accuracy of the initial program to Herbie's most accurate alternative. 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. You can add a precondition to restrict Herbie to the more important inputs in that case.

    In the plot, each individual sampled point is shown with a faint circle, and the thick line is moving average of those individual samples. The red line is the initial program and the blue line is Herbie's most accurate alternative.

    Accuracy is shown on the vertical axis, and higher is better. The horizontal axis shows one of the variables in the input program; the dropdown in the title switches between input variables. The checkboxes below the graph toggle the red and blue lines on and off.

    If Herbie decided to insert an if statement into the program, the locations of those if statements will be marked with vertical bars.

    Accuracy vs Speed

    Next, a Pareto graph and table list the alternatives Herbie found.

    Both the plot and the table show the same data. In the plot, accuracy is on the vertical axis and speedup is on the horizontal axis. Up and to the right is better. The initial program is shown with a red square, while each of Herbie's alternatives is shown with a blue circle. A faint line shows the Pareto frontier—that is, it goes through all Herbie alternatives that maximize speed for their level of accuracy. Some of Herbie's alternatives may not be on the Pareto frontier due to differences between the training and test set.

    In the table, each alternative is shown along with its accuracy and speed relative to the initial program. Values are green if they are better than the initial program, and black otherwise. Each alternative is linked to its description lower on the page.

    Initial program and Alternatives

    Below the table come a series of boxes detailing the initial program and each of Herbie's alternatives, along with their accuracy and relative speed.

    The accuracy and relative speed of each alternative is given in the title. Below the title, the alternative expression itself is given. The dropdown in the top right can be used to change the syntax used. If Herbie could not come up with anything better than the initial program, no alternatives are displayed.

    Each alternative also has a derivation, which can be shown by clicking on "Derivation". The derivation shows each step Herbie took to transform the initial program into this alternative. The initial program has no derivation.

    Each step in the derivation gives the accuracy after that step. Sometimes you can use that to pick a less-complex and not-substantially-less-accurate program. The derivation will also call out any case splits. When a part of the step is colored blue, that identifies the changed part of the expression.

    Derivations may also contain "step-by-step derivations"; you can click on those step-by-step derivations to expand them. Each step in the step-by-step derivation names an arithmetic law from Herbie's database, with metadata-eval meaning that Herbie used direct computation in a particular step.

    Derivations are intended to give you more confidence in Herbie's results and are not guaranteed to be an accurate reflection of Herbie's internal process of constructing an alternative.

    Reproduction

    Finally, Herbie gives a command you can run to reproduce its result. If you find a Herbie bug, include this code snippet when filing an issue.

    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.

    ================================================ FILE: www/doc/2.3/toc.js ================================================ function make_toc() { var headings = document.querySelectorAll("h2"); var toc = document.createElement("nav"); toc.classList.add("toc") var list = document.createElement("ul"); for (var i = 0; i < headings.length; i++) { var li = document.createElement("li"); var a = document.createElement("a"); var h = headings[i]; if (! h.id) { h.setAttribute("id", "heading-" + i); } a.setAttribute("href", "#" + h.id); a.innerHTML = h.innerHTML; li.appendChild(a); list.appendChild(li); } toc.appendChild(list); headings[0].parentNode.insertBefore(toc, headings[0]); } window.addEventListener("load", make_toc); ================================================ FILE: www/doc/2.3/tutorial.html ================================================ Herbie Tutorial

    Herbie Tutorial

    Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 in floating-point. Herbie helps find and fix these mysterious inaccuracies.

    To get started, download and install Herbie. You're then ready to begin using it.

    Giving Herbie expressions

    Start Herbie with:

    racket -l herbie web

    Alternatively, if you added herbie to the path, you can always replace racket -l herbie with just herbie.

    After a brief wait, your web browser should open and show you Herbie's main window. The most important part of the page is this bit:

    The program input field in the Herbie web UI.

    Let's start by just looking at an example of Herbie running. Click "Show an example". This will pre-fill the expression sqrt(x + 1) - sqrt(x) with x ranging from to 0 and 1.79e308.

    The input range field in the Herbie web UI.

    Now that you have an expression and a range for each variable, click the "Improve with Herbie" button. You should see the entry box gray out, and shortly thereafter some text should appear describing what Herbie is doing. After a few seconds, you'll be redirected to a page with Herbie's results.

    The very top of this results page gives some quick statistics about the alternative ways Herbie found for evaluating this expression:

    Statistics and error measures for this Herbie run.

    Here, you can see that Herbie's most accurate alternative has an accuracy of 99.7%, much better than the initial program's 53.2%, and that in total Herbie found 5 alternatives. One of those alternatives is both more accurate than the original expression and also 1.9× faster. The rest of the result page shows each of these alternatives, including details like how they were derived. These details are all documented, but for the sake of the tutorial let's move on to a more realistic example.

    Programming with Herbie

    You can use Herbie on expressions from source code, mathematical models, or debugging tools. But most users use Herbie as they write code, asking it about any complex floating-point expression they write. Herbie has options to log all the expressions you enter so that you can refer to them later.

    But to keep the tutorial focused, let's suppose you're instead tracking down a floating-point bug in existing code. Then you'll need to start by identifying the problematic floating-point expression.

    To demonstrate the workflow, let's walk through bug 208 in math.js, a math library for JavaScript. The bug deals with inaccurate square roots for complex numbers. (For a full write-up of the bug itself, check out a blog post by one of the Herbie authors.)

    Finding the problematic expression

    In most programs, there's a small kernel that does the mathematical computations, while the rest of the program sets up parameters, handles control flow, visualizes or prints results, and so on. The mathematical core is what Herbie will be interested in.

    For our example, let's start in lib/function/. This directory contains many subdirectories; each file in each subdirectory defines a collection of mathematical functions. We're interested in the complex square root function, which is defined in arithmetic/sqrt.js.

    This file handles argument checks, different types, and error handling, for both real and complex square roots. None of that is of interest to Herbie; we want to extract just the mathematical core. So skip down to 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 is the mathematical core that we want to send to Herbie.

    Converting problematic code to Herbie input

    In this code, x is of type Complex, a data structure with multiple fields. Herbie only deals with floating-point numbers, not data structures, so we will treat the input x as two separate inputs to Herbie: xre and xim. We'll also pass each field of the output to Herbie separately.

    This code also branches between non-negative x.im and negative x.im. It's usually better to send each branch to Herbie separately. So in total, this code turns into four Herbie inputs: two output fields, for each of the two branches.

    Let's focus on the first field of the output for the case of non-negative x.im.

    The variable r is an intermediate variable in this code block. Intermediate variables provide Herbie with crucial information that Herbie can use to improve accuracy, so you want to expand or inline them. The result looks like this:

    0.5 * sqrt(2.0 * (sqrt(xre * xre + xim * xim) + xre))

    Recall that this code is only run when x.im is non-negative (but it runs for all values of x.re). So, select the full range of values for x.re, but restrict the range of x.im, like this:

    Restricting the input range to xim >= 0.
    This asks Herbie to consider only non-negative values of xim when improving the accuracy of this expression. The number 1.79e308 is approximately the largest double-precision number, and will auto-complete.

    Herbie's results

    Herbie will churn for a few seconds and produce a results page. In this case, Herbie found 4 alternatives, and we're interested in the most accurate one, which should have an accuracy of 84.6%:

    Below these summary statistics, we can see a graph of accuracy versus input value. By default, it shows accuracy versus xim; higher is better:

    The drop in accuracy once xim is bigger than about 1e150 really stands out, but you can also see that Herbie's alternative more accurate for smaller xim values, too. You can also change the graph to plot accuracy versus xre instead:

    This plot makes it clear that Herbie's alternative is almost perfectly accurate for positive xre, but still has some error for negative xre.

    Herbie also found other alternatives, which are less accurate but might be faster. You can see a summary in this table:

    Herbie's algorithm is randomized, so you likely won't see the exact same thing; you might see more or fewer alternatives, and they might be more or less accurate and fast. That said, the most accurate alternative should be pretty similar.

    That alternative itself is shown lower down on the page:

    A couple features of this alternative stand out immediately. First of all, Herbie inserted an if statement. This if statement handles a phenomenon known as cancellation, and is part of why Herbie's alternative is more accurate. Herbie also replaced the square root operation with the hypot function, which computes distances more accurately than a direct square root operation.

    If you want to see more about how Herbie derived this result, you could click on the word "Derivation" to see a detailed, step-by-step explanation of how Herbie did it. For now, though, let's move on to look at another alternative.

    The fifth alternative suggested by Herbie is much less accurate, but it is about twice as fast as the original program:

    This alternative is kind of strange: it has two branches, and each one only uses one of the two variables xre and xim. That explains why it's fast, but it's still more accurate than the initial program because it avoids cancellation and overflow issues that plagued the original.

    Using Herbie's alternatives

    In this case, we were interested in the most accurate possible implementation, so let's try to use Herbie's most accurate alternative.

    // Herbie 2.1 for:
    //   0.5 * sqrt(2.0 * (sqrt(xre*xre + xim*xim) + xre))
    var r = Math.hypot(x.re, x.im);
    var re;
    if (xre + r <= 0) {
        re = 0.5 * Math.sqrt(2 * (x.im / (x.re / x.im) * -0.5));
    } else {
        re = 0.5 * Math.sqrt(2 * (x.re + r));
    }
    if (x.im >= 0) {
      return new Complex(
          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))
      );
    }

    Note that I've left the Herbie query in a comment. As Herbie gets better, you can re-run it on this original expression to see if it comes up with improvements in accuracy.

    By the way, for some languages, including JavaScript, you can use the drop-down in the top-right corner of the alternative block to translate Herbie's output to that language. However, you will still probably need to refactor and modify the results to fit your code structure, just like here.

    Next steps

    With this change, we've made this part of the complex square root function much more accurate, and we could repeat the same steps for the other branches and other fields in this program. You now have a pretty good understanding of Herbie and how to use it. Please let us know if Herbie has helped you, and check out the documentation to learn more about Herbie's various options and outputs.

    ================================================ FILE: www/doc/2.3/using-cli.html ================================================ Using Herbie from the Command Line

    Shell and Batch Mode

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

    The Herbie shell

    The Herbie shell lets you interact with Herbie: you type in input expressions and Herbie prints their more accurate versions. Run the Herbie shell with this command:

    racket -l herbie shell
    Starting Herbie 2.2 with seed 2098242187
    Find help on https://herbie.uwplse.org/, exit with Ctrl-D
    herbie> 

    Herbie prints a seed, which you can use to reproduce a Herbie run, and links you to documentation. Then, it waits for inputs, which you can type directly into your terminal in FPCore format:

    herbie> (FPCore (x) (- (+ 1 x) x))
    (FPCore (x)
      ...
      1.0)

    Herbie suggests that 1.0 is more accurate than the original expression (- (+ 1 x) x). The the ... elides additional information provided by Herbie.

    The Herbie shell only shows Herbie's most accurate variant.

    Batch processing FPCores

    Alternatively, you can run Herbie on a file with multiple expressions in it, writing Herbie's versions of each to a file. This mode is intended for use by scripts.

    racket -l herbie improve bench/tutorial.fpcore out.fpcore
    Starting Herbie 2.2 with seed 1139794558...
    Warning: 75.2% of points produce a very large (infinite) output. You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/2.0/faq.html#inf-points> for more.

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

    ;; seed: 1139794558
    
    (FPCore (x)
      :name "Cancel like terms"
      ...
      1.0)
    
    (FPCore (x)
      :name "Expanding a square"
      ...
      (fma x x (+ x x)))
    
    (FPCore (x y z)
      :name "Commute and associate"
      ...
      0.0)

    Note that output file is in the same order as the input file. For more control over Herbie, see the documentation of Herbie's command-line options.

    ================================================ FILE: www/doc/2.3/using-web.html ================================================ Using Herbie from the Browser

    The Browser UI

    Herbie rewrites floating point expressions to make them more accurate. Herbie can be used from the command-line or from the browser; this page is about 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. The web shell is the friendliest and easiest way to use Herbie.

    Start the Herbie web shell by running:

    racket -l herbie web

    After a few seconds, the web shell will start up and direct your browser to Herbie:

    racket -l herbie web
    Starting Herbie 2.2 with seed 1003430182
    Your Web application is running at http://localhost:8000/.
    Stop this program at any time to terminate the Web Server.
    A screenshot of the Herbie web shell main page.

    You can type an input expressions in standard mathematical syntax. After typing in an expression, you will be asked to specify the range of values for each input variable. Once you're done, hit the "Improve with Herbie" button to run Herbie.

    The web shell reports Herbie's progress and redirects to a report once Herbie is done.

    The web shell can also automatically save the generated reports, and has many other options you might want to explore.

    Batch report generation

    A report can also be generated directly from an input file in FPCore format:

    racket -l herbie report bench/tutorial.fpcore output/ 
    Starting Herbie 2.2 with seed 770126425...
    Warning: 25.0% of points produce a very large (infinite) output. You may want to add a precondition.
    See <https://herbie.uwplse.org/doc/latest/faq.html#inf-points> for more.
      1/3	[   0.8s]   55% → 100%	Expanding a square
      2/3	[   0.8s]  100% → 100%	Commute and associate
      3/3	[   0.9s]   53% → 100%	Cancel like terms

    This command generates a report from the input expressions in bench/tutorial.fpcore and saves the report in the directory output/. It's best if that directory doesn't exist before running this command, because otherwise Herbie may overwrite files in that directory.

    Occasionally Herbie will emit warnings, just like in the example above. All of Herbie's warnings are documented, with explanations and suggested fixes.

    The report Herbie generates is in HTML format so you'll need to start a web server. If you have Python installed, that's particularly convenient:

    python3 -m http.server -d output

    Then go to localhost:8000 in your favorite browser. The report summarizes Herbie's results for all expression in your input file, and you can click on individual expressions to see Herbie's output for them.

    Batch report generation is the best way to run Herbie on a large collection of inputs. Like the web shell, it can be customized through command-line options, including parallelizing Herbie with multiple threads.

    ================================================ FILE: www/doc.html ================================================ Herbie: Automatically Improving Floating Point Accuracy

    Herbie Documentation

    Tutorials

    Documentation

    Developer Documentation

    • Platforms: how to write a new Herbie compilation target.
    • Other APIs: advanced APIs for compilation targets.
    • HTTP API: Herbie's HTTP endpoints
    • Diagrams: miscellaneous figures related to Herbie

    Blog posts about Herbie

    ================================================ FILE: www/graph.js ================================================ margin = 10; barheight = 10; width = 505; textbar = 20; function sort_by(type) { return function(a, b) { return b[type] - a[type]; } } 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 .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[start]) + " to " + r10(precision - d[end]) + ")"; } bar.append("line") .attr("class", "guide") .attr("x1", 0) .attr("x2", function(d) { return (precision - Math.max(d[start], d[end])) / precision * width }) .attr("y1", line_y) .attr("y2", line_y); var g = bar.append("g").attr("title", title) .attr("class", function(d) { return d[start] > d[end] ? "good" : "bad" }); g.append("line") .attr("x1", function(d) {return (precision - Math.max(d[start], d[end])) / precision * width}) .attr("x2", function(d) { return (precision - Math.min(d[start], d[end])) / precision * width }) .attr("y1", line_y) .attr("y2", line_y); g.append("g").attr("transform", function(d, i) { return "translate(" + ((precision - Math.min(d[start], d[end])) / precision * width) + ", " + line_y(d, i) + ")"; }) .append("polygon").attr("points", "0,-3,0,3,5,0"); } function draw_results(node) { d3.json("results.json", function(err, data) { if (err) return console.error(err); data = data.tests; data.sort(sort_by("start")); make_graph(node, data, "start", "end"); }); } ================================================ FILE: www/index.html ================================================ Herbie: Automatically Improving Floating Point Accuracy

    The Herbie Project

    Find and fix floating-point problems:

    sqrt(x+1) - sqrt(x)1/(sqrt(x+1) + sqrt(x))
    Herbie detects inaccurate expressions and finds more accurate replacements. The red expression is inaccurate when x > 1; Herbie's replacement, in blue, is accurate for all x.
    Herbie improving accuracy on the “Hamming” benchmark suite. Longer arrows are better. Each arrow starts at the accuracy of the original expression, and ends at the accuracy of Herbie’s output, in each case on random double-precision inputs.

    Herbie Project News

    1. Herbie 2.2 is out today! The star of this release is a new platform API for pluggable compilation targets. Try it out!

    2. Today we are releasing Herbie 2.1! This release makes Herbie's generated code—and Herbie itself—faster. Try it out!

    3. We are proud to release Herbie 2.0! This release teaches Herbie to optimize for both accuracy and speed, and includes a complete redesign of Herbie's reports and metrics. Try it out!

    4. Due to a major unforseen issue with our release infrastructure, we are re-releasing Herbie 1.5. If you previously tried to install it and failed, please try again!
    5. We're pleased to announce the release of Herbie 1.5, with features like argument sorting and multiple outputs. Do try it out!
    6. Brett and Oliver are giving a talk ARITH 2021 on adding precision tuning to Herbie. Tune in or read the paper to learn more!

    7. Zach and Pavel gave a talk about Herbie's last five years: trust, measurement, community, and generality.
    8. The chaos of 2020 now brings you a rowboat of stability: Herbie 1.4, with significant speed-ups and ease of use improvements. Download and try it today!

    9. David will be talking about FPBench 1.2 and the latest improvements in Herbie at Correctness 2019 in Denver.
    10. Herbie got a shout-out in Pavel's talk at the FP analysis tutorial at SC'19.
    11. Pavel will be joining the University of Utah as an assistant professor next year, joining Ganesh and Zvonimir at what is already a nexus of floating-point research.
    12. Zach gave a keynote at CoNGA’19 on multi-precision, multi-format computations and our efforts to support them in Herbie, FPBench, and Titanic.

    13. Alex gave a talk on our sister project Herbgrind at PLDI’18. Watch it if you want to know how Herbgrind pulls inaccurate floating-point expressions out of large numeric code bases.
    14. After a year of work, Herbie 1.2 has been released. This release focuses on creativity and accuracy, with a new system to infer better branches and more accurate defaults for Herbie's various parameters. Read about all the changes in the release notes.
    15. We teamed up with Heiko and Eva on the Daisy team to combine our tools and evaluate how best to use them together—it'll be published at FM’18. If you're using Herbie with other floating point tools, let us know!
    16. Pavel and Zach went to see Herbie Hancock play at the Seattle Center. Watching Herbie play Chameleon on the keytar is sure to inspire the next generation of floating point accuracy improvement!
    17. Pavel gave a talk at Microsoft Research Redmond on Herbie and Herbgrind, plus the FPBench project. Thank you everyone who came!
    18. Pavel wrote a retrospective on the early history of Herbie, and some lessons learned.

    19. Pavel gave a talk at MPI-SWS Saarbrücken on Herbie, Herbgrind, lessons learned, and what comes next. Thank you Eva Darulova and her students for the invitation and the warm welcome. The video was recorded and can be watched on YouTube.
    20. After incubating on this website, Herbgrind has moved to a new website hosted at UCSD, where Alex, Herbie star and the main Herbgrind developer, is now doing his PhD. We'll continue our close collaboration, including in the FPBench project, and are hoping the new, more-focused websites help users.
    21. Just one month after the beta, Herbie 1.1 has been released. This release adds a browser interface for Herbie, and includes significant bug fixes, usability tweaks, and improvements. Read about all the changes in the release notes.
    22. Our sister project Herbgrind has released version 0.42. This pre-release is a reworked, faster, and more stable Herbgrind, which can find root causes for floating-point errors in the largest and gnarliest of codebases!
    23. After months of work, a beta of Herbie 1.1 has been released. This release adds a browser interface for Herbie, and includes significant bug fixes, usability tweaks, and improvements. Read about all the changes in the release notes.
    24. Zach is giving a talk at the University of Utah about Herbie, FPBench, and Herbgrind. Please come to learn about automated tools for floating point!

    25. After months of work, the Herbie developers are proud to announce the release of Herbie 1.0. This release transitions to the FPCore format from the FPBench initiative, and includes significant bug fixes, usability tweaks, and improvements. Read about all the changes in the release notes.
    26. In preparation for the Version 1.0 release, we've renamed the pi and e constants to upper case. This matches libm and should make it a little harder to cause bugs. Herbie will now optimize expressions like (exp 1) to E.
    27. We're proud to announce that we've been collaborating with Prof. Martel and his students to build a common benchmark suite and format for floating point tools. Version 1.0 of Herbie will support only the FPBench format.
    28. Pavel is giving a talk at Google on how Herbie works and what our plans for the future are.
    29. Pavel is giving a talk at MIT on how Herbie works internally.
    30. In preparation for the Version 1.0 release, we've renamed several functions in Herbie to match the libm names. In particular, look out for abs, which is now fabs, and expt, which is now pow.
    31. Pavel is giving a talk at MathWorks on how Herbie works answered questions on how it could be extended.
    32. The Herbie Rust Linter plugs into the Rust compiler to add warnings for numerically unstable expressions, and suggests Herbie's more accurate output as a hint.

    33. The Herbie GHC Plugin by Mike Izbicki automatically runs Herbie on applicable expressions in a Haskell program. He's also scanned all of Stackage LTS-3.5 for numerical inaccuracies with Herbie.
    34. Pavel is giving a Distinguished Paper talk at PLDI’15 on the scientific advances that underpin Herbie.
    35. Zach is giving a talk at Berkeley on how we plan to improve floating point accuracy with Herbie.

    36. Pavel is giving a talk at OPLSS on whether floating point accuracy can be improved, and our plans for finding out.
    37. Pavel is giving a lightning talk on a new project to improve the accuracy of floating point expressions.
    38. Pavel is giving a talk on at Dropbox on a new project to improve the accuracy of floating point expressions. (video)

    The Herbie Developers

    Herbie is developed at UW PLSE, with contributions from a supportive community.

    The main contributors are Pavel Panchekha, Alex Sanchez-Stern, David Thien, Zachary Tatlock, Jason Qiu, Jack Firth, and James R. Wilcox.

    ================================================ FILE: www/main.css ================================================ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@400,700&family=IBM+Plex+Mono&family=Ruda:wght@600&display=swap'); html { font-family: 'IBM Plex Serif', serif; font-size: 16px; line-height: 1.4; } body { max-width: 650px; margin: .5em auto 3em;} h1, h2, h3 { font-family: 'Ruda', sans-serif; letter-spacing: .06ch; font-weight: 600; clear: both; } h1 {font-size: 20px; line-height: 1; margin: 4em 0 .5em; } h2 {font-size: 18px; line-height: 1.125; margin: 3em 0 .25em; } h3 {font-size: 17px; line-height: 1.286; margin: 1.5em 0 .2em; font-weight: 400; } p, li, dd, blockquote, figcaption, summary { text-align: justify; -moz-hyphens: auto; -webkit-hyphens: auto; hyphens: auto; margin: 0 0 1em 0; } dd { margin: .5em 0 1em; } .showcase { background: #ddd; padding: 1em; margin: 4em 0; clear: both;} .showcase > h2 { margin: 0 0 1em; } .showcase figcaption {font-size: 15px; line-height: 1.4; margin-top: 1em;} .showcase img { width: 100%; } .before-after { font-size: 20px; text-align: center; line-height: 1.5; } .before-after code { padding: 0 1ex; font-weight: bold; } .before-after .before { color: #aa2e00; } .before-after .after { color: #0000e0; } header { line-height: 2; border-bottom: 1px solid #ddd; margin-bottom: 2em; display: flex; flex-direction: row; align-items: bottom; } header h1 { width: calc(50% - 54px/2); margin: 0; font-size: 125%; line-height: 1.6 } header img { width: 54px; height: 24px; vertical-align: bottom; padding-bottom: 4px; } header nav { width: calc(50% - 54px/2); text-align: right; } header ul { margin: 0; padding: 0; font-weight: bold; } header li { display: inline-block; margin: 0 .5em; } header li::before { content: "•"; margin-right: 1em; } header li:first-child::before { content: none; } .toc { margin: 2em 0; background: #eee; padding: 1em; } .toc::before { content: "Table of Contents"; font-size: 110%; font-weight: bold; } .toc ul { list-style: outside none; padding: 0; } svg {margin: 0 auto; display: block;} pre { padding-left: 2em; font-size: 16px; font-family: 'IBM Plex Mono', monospace; } pre.shell:before { content: "$"; margin-right: 1ex; font-weight: bold; } div.column-container { display: flex; justify-content: space-around;} div.column-container h3 { margin: 0 0 1em; } div.column-container ul { list-style: inside none; margin: 0; padding: 0; } ul {padding-left: 1em;} a {color: #2A6496; text-decoration: none} a:hover {text-decoration: underline; color: #295785} #formula input { width: 100%; font-size: 125%; } #errors li { color: #800; } #progress { font-size: 14px; font-family: sans-serif; background-color: #f1f1f1; padding-left: 0; overflow: scroll; overflow-y: auto; } #num-jobs { font-weight: bold; } dl { margin: 1em 0; } dl dd { margin: .5em 1em; } dl.function-list dt { font-weight: bold; float: left; width: 220px; clear: left; margin-bottom: .5em; } dl.function-list dd { clear: right; margin: 0; } dl.function-list dd:after { clear: both; height: 1px; display: block; content: "."; visibility: hidden; margin: 0;} table.function-list { width: 100%; margin: 1em; } table.function-list th { text-align: left; } table.function-list td:nth-child(1) { font-family: monospace; min-width: 170px; margin: 0 1em .5em 1em; } h2 .badges { color: #2A6496; margin-left: 1ex; float: right; } .video { display: block; margin: auto; } .abstract { padding: 1em 3em; position: relative; } .abstract:before { content: "“"; font-size: 600%; position: absolute; top: 0; left: 0; font-family: serif; color: #ccc; } .abstract:after { content: "”"; font-size: 600%; position: absolute; bottom: -.5em; right: 0; font-family: serif; color: #ccc; } .paper-thumb { width: 30%; float: left; margin: 1em; } .paper-thumb img { width: 100%; border: 1px solid #ccc; } #news { list-style: none inside; padding: 0; position: relative; } #news li { margin-bottom: 12px; margin-left: 80px; } #news time { font-weight: bold; text-align: right; position: absolute; left: 0; display: block; width: 75px; } #news time:after { content: ":"; } #news .yearmark { border: 0; border-top: 1px solid #bbb; margin: 1em 0; width: 90px; } #news .more { color: #2A6496; text-decoration: none; cursor: pointer; margin-left: 0; text-align: center; } #news .more::before, #news .more::after { content: "⇩"; padding: 1ex; } #news .more:hover {text-decoration: underline; color: #295785} body > img { width: 100%; display: block; margin: 2em 0; } @media (max-width: 600px) { body { padding: 1em; margin: 0 auto;} .showcase, body > img { margin-left: -1em; margin-right: -1em;} body > img { width: calc(100% + 2em); } } .warning { background: #fffdbb; border: 3px solid #E9DA8A; padding: 5px; } ================================================ FILE: www/papers.html ================================================ Herbie Papers

    Herbie Related Papers

    This page describes papers related to the Herbie project, either because they describe Herbie and its internals or because they were inspired by our work on Herbie. In total, this page lists over a dozen publications, including papers in  major venues and which received 🏆 awards.

    PLDI’15: Herbie ★ 🏆

    Automatically Improving Accuracy for Floating Point Expressions was published at ★ PLDI 2015, where it won the 🏆 Distinguished Paper Award. The paper introduces Herbie, describes its internals, and relates experiments describing its effectiveness.

    Scientific and engineering applications depend on floating point arithmetic to approximate real arithmetic. This approximation introduces rounding error, which can accumulate to produce unacceptable results. While the numerical methods literature provides techniques to mitigate rounding error, applying these techniques requires manually rearranging expressions and understanding the finer details of floating point arithmetic.

    We introduce Herbie, a tool which automatically discovers the rewrites experts perform to improve accuracy. Herbie's heuristic search estimates and localizes rounding error using sampled points (rather than static error analysis), applies a database of rules to generate improvements, takes series expansions, and combines improvements for different input regions. We evaluated Herbie on examples from a classic numerical methods textbook, and found that Herbie was able to improve accuracy on each example, some by up to 60 bits, while imposing a median performance overhead of 40%. Colleagues in machine learning have used Herbie to significantly improve the results of a clustering algorithm, and a mathematical library has accepted two patches generated using Herbie.

    The paper, video abstract, conference talk, and conference talk slides are available.

    NSV’16: FPBench

    Toward a Standard Benchmark Format and Suite for Floating-Point Analysis was published at NSV'16. The paper presents the motivation behind FPBench and describes a preliminary version of the FPBench standards.

    We introduce FPBench, a standard benchmark format for validation and optimization of numerical accuracy in floating-point computations. FPBench is a first step toward addressing an increasing need in our community for comparisons and combinations of tools from different application domains. To this end, FPBench provides a basic floating-point benchmark format and accuracy measures for comparing different tools. The FPBench format and measures allow comparing and composing different floating-point tools. We describe the FPBench format and measures and show that FPBench expresses benchmarks from recent papers in the literature, by building an initial benchmark suite drawn from these papers. We intend for FPBench to grow into a standard benchmark suite for the members of the floating-point tools research community.

    The paper, conference talk, and conference talk slides are available. The standards presented in the paper have grown to become the FPBench project.

    PLDI’18: Herbgrind

    Finding Root Causes of Floating Point Error was published at ★ PLDI 2018. The paper describes how Herbgrind tracks error and discovers root causes for floating-point error.

    Floating-point arithmetic plays a central role in science, engineering, and finance by enabling developers to approximate real arithmetic. To address numerical issues in large floating-point applications, developers must identify root causes, which is difficult because floating-point errors are generally non-local, non-compositional, and non-uniform.

    This paper presents Herbgrind, a tool to help developers identify and address root causes in numerical code written in low-level languages like C/C++ and Fortran. Herbgrind dynamically tracks dependencies between operations and program outputs to avoid false positives and abstracts erroneous computations to simplified program fragments whose improvement can reduce output error. We perform several case studies applying Herbgrind to large, expert-crafted numerical programs and show that it scales to applications spanning hundreds of thousands of lines, correctly handling the low-level details of modern floating point hardware and mathematical libraries and tracking error across function boundaries and through the heap.

    The paper, conference talk, and conference talk slides are available. Herbgrind is available on its website.

    FM’18 short: Daisy/Herbie

    Combining Tools for Optimization and Analysis of Floating-Point Computations was published at FM'18'. The paper describes how different floating-point analysis tools can be combined using tools provided by the FPBench project.

    Recent renewed interest in optimizing and analyzing floating-point programs has lead to a diverse array of new tools for numerical programs. These tools are often complementary, each focusing on a distinct aspect of numerical programming. Building reliable floating point applications typically requires addressing several of these aspects, which makes easy composition essential. This paper describes the composition of two recent floating-point tools: Herbie, which performs accuracy optimization, and Daisy, which performs accuracy verification. We find that the combination provides numerous benefits to users, such as being able to use Daisy to check whether Herbie's unsound optimizations improved the worst-case roundoff error, as well as benefits to tool authors, including uncovering a number of bugs in both tools. The combination also allowed us to compare the different program rewriting techniques implemented by these tools for the first time. The paper lays out a road map for combining other floating-point tools and for surmounting common challenges.

    The paper is available.

    Correctness’19: Multi-precision Herbie

    Toward Multi-Precision, Multi-Format Numerics was published at Correctness'19. The paper describes Herbie's model of multi-precision/multi-format code.

    Recent research has provided new, domain-specific number systems that accelerate modern workloads. Using these number systems effectively requires analyzing subtle multi-format, multi-precision (MPMF) code. Ideally, recent programming tools that automate numerical analysis tasks could help make MPMF programs both accurate and fast. However, three key challenges must be addressed: existing automated tools are difficult to compose due to subtle incompatibilities; there is no "gold standard" for correct MPMF execution; and no methodology exists for generalizing existing, IEEE-754-specialized tools to support MPMF. In this paper we report on recent work towards mitigating these related challenges. First, we extend the FPBench standard to support multi-precision, multi-format (MPMF) applications. Second, we present Titanic, a tool which provides reference results for arbitrary MPMF computations. Third, we describe our experience adapting an existing numerical tool to support MPMF programs.

    The paper is available.

    NSV’20: Towards Numerical Assistants 🏆

    Towards Numerical Assistants: Trust, Measurement, Community, and Generality for the Numerical Workbench was published at NSV'20, where it was an 🏆 invited talk. The talk described the goals driving Herbie's development up to 2020.

    The last few years have seen an explosion of work on tools that address numerical error in scientific, mathematical, and engineering software. The resulting tools can provide essential guidance to expert non-experts: scientists, mathematicians, and engineers for whom mathematical computation is essential but who may have little formal training in numerical methods. It is now time for these tools to move into practice.

    Practitioners need a "numerical workbench" that not only succeeds as a research artifact but as a daily tool. We describe our experience adapting Herbie, a tool for numerical error repair, from a research prototype to a reliable workhorse for daily use. In particular, we focus on how we worked to increase user trust and use internal measurement to polish the tool. Looking more broadly, we show that community development and an investment in the generality of our tools, such as through the FPBench project, will better support users and strengthen our research community.

    The conference talk and conference talk slides are available.

    POPL’21: Egg ★ 🏆

    egg: Fast and extensible equality saturation was published at ★ PLDI 2021, where it won the 🏆 Distinguished Paper Award. The paper describes a new library for equality saturation used by Herbie.

    An e-graph efficiently represents a congruence relation over many expressions. Although they were originally developed in the late 1970s for use in automated theorem provers, a more recent technique known as equality saturation repurposes e-graphs to implement state-of-the-art, rewrite-driven compiler optimizations and program synthesizers. However, e-graphs remain unspecialized for this newer use case. Equality saturation workloads exhibit distinct characteristics and often require ad-hoc e-graph extensions to incorporate transformations beyond purely syntactic rewrites.

    This work contributes two techniques that make e-graphs fast and extensible, specializing them to equality saturation. A new amortized invariant restoration technique called rebuilding takes advantage of equality saturation's distinct workload, providing asymptotic speedups over current techniques in practice. A general mechanism called e-class analyses integrates domain-specific analyses into the e-graph, reducing the need for ad hoc manipulation.

    We implemented these techniques in a new open-source library called egg. Our case studies on three previously published applications of equality saturation highlight how egg's performance and flexibility enable state-of-the-art results across diverse domains.

    The paper, video abstract, and conference talk are available.

    ARITH’21: Pareto Herbie

    Combining Precision Tuning and Rewriting was published at ★ ARITH 2021. The paper introduces Herbie's pareto mode and describes the modification to Herbie that enable it.

    Precision tuning and rewriting can improve both the accuracy and speed of floating point expressions, yet these techniques are typically applied separately. This paper explores how finer-grained interleaving of precision tuning and rewriting can help automatically generate a richer set of Pareto-optimal accuracy versus speed trade-offs.

    We introduce Pherbie (Pareto Herbie), a tool providing both precision tuning and rewriting, and evaluate interleaving these two strategies at different granularities. Our results demonstrate that finer-grained interleavings improve both the Pareto curve of candidate implementations and overall optimization time. On a popular set of tests from the FPBench suite, Pherbie finds both implementations that are significantly more accurate for a given cost and significantly faster for a given accuracy bound compared to baselines using precision tuning and rewriting alone or in sequence.

    The paper, conference talk, and conference talk slides are available.

    OOPSLA’21: Ruler ★ 🏆

    Rewrite Rule Inference Using Equality Saturation was published in OOPSLA'21, where it won the 🏆 Distinguished Paper Award. The paper describes how rewrite rules, like the ones Herbie uses, can be synthesized from examples.

    Many compilers, synthesizers, and theorem provers rely on rewrite rules to simplify expressions or prove equivalences. Developing rewrite rules can be difficult: rules may be subtly incorrect, profitable rules are easy to miss, and rulesets must be rechecked or extended whenever semantics are tweaked. Large rulesets can also be challenging to apply: redundant rules slow down rule-based search and frustrate debugging.

    This paper explores how equality saturation, a promising technique that uses e-graphs to apply rewrite rules, can also be used to infer rewrite rules. E-graphs can compactly represent the exponentially large sets of enumerated terms and potential rewrite rules. We show that equality saturation efficiently shrinks both sets, leading to faster synthesis of smaller, more general rulesets.

    We prototyped these strategies in a tool dubbed Ruler. Compared to a similar tool built on CVC4, Ruler synthesizes 5.8× smaller rulesets 25× faster without compromising on proving power. In an end-to-end case study, we show Ruler-synthesized rules which perform as well as those crafted by domain experts, and addressed a longstanding issue in a popular open source tool.

    The paper and conference talk are available.

    Correctness’21: Rising Heterogeneity

    Guarding Numerics Amidst Rising Heterogeneity was published at Correctness'21. The paper warns that Herbie and similar tools must adapt to hardware accelerators and GPUs.
    New heterogeneous computing platforms—especially GPUs and other accelerators—are being introduced at a brisk pace, motivated by the goals of exploiting parallelism and reducing data movement. Unfortunately, their sheer variety as well as the optimization options supported by them have been observed to alter the computed numerical results to the extent that reproducible results are no longer possible to obtain without extra effort. Our main contribution in this paper is to document the scope and magnitude of this problem which we classify under the heading of numerics. We propose a taxonomy to classify specific problems to be addressed by the community, a few immediately actionable topics as the next steps, and also forums within which to continue discussions.

    The paper is available.

    EGRAPHS’22: Synthesizing Identities

    Synthesizing mathematical identities with e-graphs was published at EGRAPHS'22. The paper describes how identities derived from mathematical expressions can be used to improve accuracy.

    Identities compactly describe properties of a mathematical expression and can be leveraged into faster and more accurate function implementations. However, identities must currently be discovered manually, which requires a lot of expertise. We propose a two-phase synthesis and deduplication pipeline that discovers these identities automatically. In the synthesis step, a set of rewrite rules is composed, using an e-graph, to discover candidate identities. However, most of these candidates are duplicates, which a secondary de-duplication step discards using integer linear programming and another e-graph. Applied to a set of 61 benchmarks, the synthesis phase generates 7 215 candidate identities which the de-duplication phase then reduces down to 125 core identities.

    The paper is available.

    PLDI’22: OpTuner

    Choosing Mathematical Function Implementations for Speed and Accuracy was published at PLDI'22. The paper describes how multiple implementations of functions like sin can be combined to achieve better combinations of accuracy and precision.

    Standard implementations of functions like sin and exp optimize for accuracy, not speed, because they are intended for general-purpose use. But just like many applications tolerate inaccuracy from cancellation, rounding error, and singularities, many application could also tolerate less-accurate function implementations. This raises an intriguing possibility: speeding up numerical code by using different function implementations.

    This paper thus introduces OpTuner, an automated tool for selecting the best implementation for each mathematical function call site. OpTuner uses error Taylor series and integer linear programming to compute optimal assignments of 297 function implementations to call sites and presents the user with a speed-accuracy Pareto curve. In a case study on the POV-Ray ray tracer, OpTuner speeds up a critical computation by 2.48×, leading to a whole program speedup of 1.09× with no change in the program output; human efforts result in slower code and lower-quality output. On a broader study of 36 standard benchmarks, OpTuner demonstrates speedups of 2.05× for negligible decreases in accuracy and of up to 5.37× for error-tolerant applications.

    The conference talk is available.

    FMCAD’22: Egg Proofs

    Small Proofs from Congruence Closure was published in FMCAD'22. The paper describes how egg computes auditable proofs of the rewrites it performs, which are used in Herbie as a debugging tool.

    Satisfiability Modulo Theory (SMT) solvers and equality saturation engines must generate proof certificates from e-graph-based congruence closure procedures to enable verification and conflict clause generation. Smaller proof certificates speed up these activities. Though the problem of generating proofs of minimal size is known to be NP-complete, existing proof minimization algorithms for congruence closure generate unnecessarily large proofs and introduce asymptotic overhead over the core congruence closure procedure. In this paper, we introduce an O(n5) time algorithm which generates optimal proofs under a new relaxed “proof tree size” metric that directly bounds proof size. We then relax this approach further to a practical O(n log(n)) greedy algorithm which generates small proofs with no asymptotic overhead. We implemented our techniques in the egg equality saturation toolkit, yielding the first certifying equality saturation engine. We show that our greedy approach in egg quickly generates substantially smaller proofs than the state-of-the-art Z3 SMT solver on a corpus of 3 760 benchmarks.

    The paper and conference talk are available.

    UIST’23: Odyssey

    Odyssey: An Interactive Workbench for Expert-Driven Floating-Point Expression Rewriting was published in UIST’23. This paper describes an experimental new user interface for Herbie.

    In recent years, researchers have proposed a number of automated tools to identify and improve floating-point rounding error in mathematical expressions. However, users struggle to effectively apply these tools. In this paper, we work with novices, experts, and tool developers to investigate user needs during the expression rewriting process. We find that users follow an iterative design process. They want to compare expressions on multiple input ranges, integrate and guide various rewriting tools, and understand where errors come from. We organize this investigation's results into a three-stage workflow and implement that workflow in a new, extensible workbench dubbed Odyssey. Odyssey enables users to: (1) diagnose problems in an expression, (2) generate solutions automatically or by hand, and (3) tune their results. Odyssey tracks a working set of expressions and turns a state-of-the-art automated tool "inside out," giving the user access to internal heuristics, algorithms, and functionality. In a user study, Odyssey enabled five expert numerical analysts to solve challenging rewriting problems where state-of-the-art automated tools fail. In particular, the experts unanimously praised Odyssey’s novel support for interactive range modification and local error visualization.

    The paper and application are available.

    EGRAPHS’23: Using egglog to Improve Floating-point Error

    egglog In Practice: Automatically Improving Floating-point Error was published in the workshop EGRAPHS'23. The paper describes how to use egglog to optimize floating-point error in programs.

    Herbie is a tool for automatically improving floating-point accuracy in programs. Egglog is a new language for performing rewriting over equality, supporting robust analysis. In this tutorial, we show how we improved Herbie in two ways. First, we will show how we leverage egglog to perform sound rewriting in the presence of division for Herbie. Second, we show how to use egglog's powerful rules to extract more accurate programs from the database.

    ARITH’23: Rival

    Making Interval Arithmetic Robust to Overflow was published at ARITH'23. This paper describes Herbie's interval arithmetic library and specifically how it deals with overflow to infinity.

    In theory, interval arithmetic at high precision can compute mathematical expressions to any required accuracy. An arbitrary precision library like MPFR can thus be used to evaluate real-valued expressions. In practice, however, MPFR's maximum representable exponent means some inputs cannot be evaluated to the required accuracy. This paper introduces movability flags, which soundly detect the majority of these inputs. Movability flags are set when overflow occurs and track whether recomputing at higher precision could possibly result in narrow intervals. In our tests on 481 expressions, movability flags detect 81.0% of unsamplable inputs. Compared to Mathematica, our movability-flag-enhanced interval arithmetic library resolves 60.3% more challenging inputs, returns 7.6× fewer completely indeterminate results, and avoids 64 cases of fatal error.

    A long preprint, documentation, and source code is available.

    POPL’24: MegaLibm ★ 🏆

    Implementation and Synthesis of Math Library Functions was published at POPL'24, where it won the 🏆 Distinguished Paper Award. This paper describes a DSL for implementing library functions such as sin and log.

    Achieving speed and accuracy for math library functions like exp, sin, and log is difficult. This is because low-level implementation languages like C do not help math library developers catch mathematical errors, build implementations incrementally, or separate high-level and low-level decision making. This ultimately puts development of such functions out of reach for all but the most experienced experts. To address this, we introduce MegaLibm, a domain-specific language for implementing, testing, and tuning math library implementations. MegaLibm is safe, modular, and tunable. Implementations in MegaLibm can automatically detect mathematical mistakes like sign flips via semantic well-formedness checks, and components like range reductions can be implemented in a modular, composable way, simplifying implementations. Once the high-level algorithm is done, tuning parameters like working precisions and evaluation schemes can be adjusted through orthogonal tuning parameters to achieve the desired speed and accuracy. MegaLibm also enables math library developers to work interactively, compiling, testing, and tuning their implementations and invoking tools like Sollya and type-directed synthesis to complete components and synthesize entire implementations. MegaLibm can express 8 state-of-the-art math library implementations with comparable speed and accuracy to the original C code, and can synthesize 5 variations and 3 from-scratch implementations with minimal guidance.

    A preprint, talk, source code are available.

    arXiv: Rival Evaluation

    Fast Real Evaluation Through Sound Mixed-Precision Tuning. This paper describes how Herbie accurately evaluates mathematical expressions and the optimizations that make this process fast.

    Evaluating a real-valued expression to high precision is a key building block in computational mathematics, physics, and numerics. A typical implementation uses a uniform precision for each operation, and doubles that precision until the real result can be bounded to some sufficiently narrow interval. However, this is wasteful: usually only a few operations really need to be performed at high precision, and the bulk of the expression could use much lower precision. Uniform precision can also waste iterations discovering the necessary precision and then still overestimate by up to a factor of two. We propose to instead use mixed-precision interval arithmetic to evaluate real-valued expressions. A key challenge is deriving the mixed-precision assignment both soundly and quickly. To do so, we introduce a sound variation of error Taylor series and condition numbers, specialized to interval arithmetic, that can be evaluated with minimal overhead thanks to an "exponent trick". Our implementation, Reval, achieves a speed-up of 1.25x compared to the state-of-the-art Sollya tool, with the speed-up increasing to 2.99x on the most difficult input points. An examination of the precisions used with and without precision tuning shows that the speed-up results come from quickly assigning lower precisions for the majority of operations.

    A paper, documentation, and source code are available.

    ARITH’25: ExplaniFloat

    Mixing Condition Numbers and Oracles for Accurate Floating-point Debugging was published at ★ ARITH 2025. The paper introduces ExplaniFloat, a numerical debugger that combines performant numerical oracles and sound error analysis for fast and accurate floating-point debugging.

    Recent advances have made numeric debugging tools much faster by using double-double oracles, and numeric analysis tools much more accurate by using condition numbers. But these techniques have downsides: double-double oracles have correlated error so miss floating-point errors while condition numbers cannot cleanly handle over- and under- flow. We combine both techniques to avoid these downsides. Our combination, ExplaniFloat, computes condition numbers using double-double arithmetic, which avoids correlated errors. To handle over- and under- flow, it introduces a separate logarithmic oracle. As a result, ExplaniFloat achieves a precision of 80.0% and a recall of 96.1% on a collection of 546 difficult numeric benchmarks: more accurate than double-double oracles yet dramatically faster than arbitrary-precision condition number computations.

    A paper, and source code are available.

    ================================================ FILE: www/pldi15-slides.key ================================================ [File too large to display: 17.0 MB] ================================================ FILE: www/results.json ================================================ {"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}]}