basedpl 0.1.69__tar.gz → 0.1.71__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/ci.yml +1 -3
- {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/speed.yml +0 -2
- {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/test.yml +0 -2
- {basedpl-0.1.69 → basedpl-0.1.71}/Cargo.lock +9 -9
- {basedpl-0.1.69 → basedpl-0.1.71}/Cargo.toml +3 -3
- {basedpl-0.1.69 → basedpl-0.1.71}/DEV.md +7 -7
- {basedpl-0.1.69 → basedpl-0.1.71}/PKG-INFO +1 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/downstile.qmd +2 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/upstile.qmd +2 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/llms.txt +1 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/bpltests.py +43 -28
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/notebook_runner.py +2 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/bindings.rs +2 -2
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/session.rs +3 -4
- {basedpl-0.1.69 → basedpl-0.1.71}/src/number.rs +1 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/src/timing.rs +13 -6
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/core.bpl +5 -1
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/speed.txt +203 -265
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm-pkg.mjs +3 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm-speed.mjs +7 -3
- {basedpl-0.1.69 → basedpl-0.1.71}/.cargo/config.toml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/.gitattributes +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/docs.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/.gitignore +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/CONTRIBUTING.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/LICENSE +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/_config.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/_layouts/default.html +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/build.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.xml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/BasedPL-de.icns +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/BasedPL-de.keylayout +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/version.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/BasedPL-es.icns +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/BasedPL-es.keylayout +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/version.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/BasedPL-fr.icns +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/BasedPL-fr.keylayout +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/version.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/BasedPL-uk.icns +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/BasedPL-uk.keylayout +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/version.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Info.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/BasedPL-us.icns +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/BasedPL-us.keylayout +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/version.plist +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/vim/ftdetect/bpl.vim +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/vim/syntax/bpl.vim +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/build.ps1 +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/install.ps1 +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.c +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.def +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.rc +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/layout.h +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-commonmark/cell-17-output-1.svg +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-commonmark/cell-18-output-1.svg +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-html/cell-17-output-1.svg +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-html/cell-18-output-1.svg +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/LICENSE-april +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/array.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/dyalog.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/graph.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/numeric.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/power.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/regex.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/string.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/lib/tree.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/.gitignore +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/_quarto.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/arrays.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/assignment.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bpl.xml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bqn.xml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bracket-types.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/cli.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/comparison.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/data.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dfns.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/distributions.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/driver.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dyalog.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dyalog.xml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/evaluation.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/LICENSE-SAX2 +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/SAX2B.ttf +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/SAX2B.woff2 +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts.css +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/attractor.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/boids.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/dragon.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/flow.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/mandelbrot.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/mandelbrot32.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/orbits.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/phyllotaxis.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/prefs.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/reaction.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/getting-started.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/_metadata.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/alpha-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/alpha.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/at.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/backslash-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/backslash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/braces.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/brackets.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/bullet.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-backslash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-divide.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-slash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-star.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-stile.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-times.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/colon.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/comma-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/comma.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del-diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del-stile.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/delta-stile.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diamond-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diamond.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/domino.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/dot.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-shoe.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-tack.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/epsilon-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/epsilon.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equal-underbar-slash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equal-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equals.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/error-guard.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/exclamation.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/greater-than-or-equal.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/greater-than.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/hash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/hydrant.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/infinity.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/inverted-question.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/iota-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/iota.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/jot-diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/jot.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/lamp.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-shoe-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-shoe.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-tack.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-tail-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/less-than-or-equal.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/less-than.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/minus.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/not-equals.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/obelus.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/omega-underbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/omega.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/overbar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/parentheses.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/partial.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/pi.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/plus.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-colon.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-diamond.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-equal.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-jot.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/question.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quote.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/radical.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/rho.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-arrow-from-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-left-arrows.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-shoe.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-tack.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-tail-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/slash-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/slash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/squad.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/star-diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/star.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/stile-tilde.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/stile.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/superscript-b.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/thorn.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/tilde-diaeresis.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/tilde.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/times.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-down-arrow.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-shoe.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-tack.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/vee-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/vee.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/wedge-bar.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/wedge.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/zilde.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs-study.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hanzi-notation-zh.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hanzi-notation.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hpl.xml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/index.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/indexing.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/introspection.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/keyboard.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/keyed.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/magics.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/modules.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/nbdev.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/notebooks.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/numbers.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/overview.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/aplwiki-arrays.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/brickman-depth-1-scalars.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/mercer-based-system.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/bpl.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/gallery.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/hpl.tmLanguage.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/monaco-worker.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/page.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/worker.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/plot.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/principles.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/processes.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/puppy.jpg +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/python.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/regex.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/repl.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/scripts.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/sidebar.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/strings.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/01_basics.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/02_functions.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/03_operators.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/04_nested-arrays.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/05_searching-indexing.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/06_rearranging-arrays.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/07_numerical-tools.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/style.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/styles.css +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/syntax.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/_metadata.yml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/c.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/canvas.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/constants.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/copy.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/csv.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/date.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/decompose.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/deflate.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/delay.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/detach.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/distribution.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/edge.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/element.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/ex.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/explain.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/fetch.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/float.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/hash.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/host.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/image.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/index.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/js.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/json.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/literal.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/load.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/memory.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/metadata.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/mime.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/mkdir.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nc.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nget.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nl.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/normalize.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nput.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/path.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/plot.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/prefs.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/r.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/rand.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/readdir.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/remove.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/rename.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/signal.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/src.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/storage.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/svg.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/time.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/ucs.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/uuid.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/vfi.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/xml.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system-functions.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/terms.qmd +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/web.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/nbs/xml.ipynb +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/package-lock.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/package.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/aplcart.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/april.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/bqn.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/core.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/dyalog.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/provenance/ngn.jsonl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/py/Cargo.toml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/py/build.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/py/src/lib.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/pyproject.toml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/__init__.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/_modidx.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/bpl.tmLanguage.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/dyalog.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/editors.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/font.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/fonts.css +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/functions.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/hpl.tmLanguage.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/input.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/ipython.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/keyboards.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/keyboards.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/layout.json +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/lb.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/notebooks.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/printing.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/reference.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/syntax.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/windows_keyboard.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/worker.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/rustfmt.toml +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/scripts/install-macos.sh +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/scripts/prep.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/agreement.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/array/storage.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/array/text.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/array.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/batch.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/canvas.js +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/canvas.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/csv.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/data.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/date.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/derivative.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/display.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/distribution.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/element.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/error.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/assign.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/binder.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/dissect.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/idioms.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/operators.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/tree.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/eval.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/execution.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/explain.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/files.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/fusion/blocks.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/fusion.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/host.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/host_web.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/image/codecs.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/image/imageio.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/image.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/inspection.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/js.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/json.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/keyed.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/lib.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/linear.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/name.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/number_theory.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/optimize.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/pervasive.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/plot.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/polynomial.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/indexing.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/numeric.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/search.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/structural.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/protocol.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/reference.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/regex.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/search.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/selection.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/settings.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/symbols.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/syntax.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/system.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/transform.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/src/xml.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/common/mod.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/conftest.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/core.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/derivatives.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/explain.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/highlighting.mjs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/input.mjs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/instructions.txt +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-aplcart +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-april +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-bqn +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-bqncrate +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-dyalog +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-ngn +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/README.md +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/aplcart.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/april.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/bqn.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/distributions.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/dyalog.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/graphics.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/lib.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/ngn.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/regex.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/static-int32.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/static.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/april.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/avl.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/redblack.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/sbst.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/splay.bpl +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/scaling.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/settings.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/speed.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_api.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_basic.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_bpltests.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_glyphs.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_graphics.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_instructions.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_ipython.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_kernel.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_keylayout.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_notebooks.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_reference.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_repl.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_windows_keyboard.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_worker.py +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/units.rs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm.mjs +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/tests/windows_keyboard.c +0 -0
- {basedpl-0.1.69 → basedpl-0.1.71}/wheel/data/share/jupyter/kernels/bpl/kernel.json +0 -0
|
@@ -38,9 +38,7 @@ jobs:
|
|
|
38
38
|
- name: Time the release build
|
|
39
39
|
continue-on-error: true
|
|
40
40
|
timeout-minutes: 20
|
|
41
|
-
run:
|
|
42
|
-
echo "model name : $(if [ "$RUNNER_OS" = macOS ]; then sysctl -n machdep.cpu.brand_string; else lscpu | sed -n 's/^Model name: *//p' | head -1; fi)"
|
|
43
|
-
bin/${{ matrix.binary }} --costs tests/speed.txt
|
|
41
|
+
run: bin/${{ matrix.binary }} --costs tests/speed.txt
|
|
44
42
|
- uses: actions/upload-artifact@v7
|
|
45
43
|
with:
|
|
46
44
|
name: wheels-${{ matrix.os }}
|
|
@@ -16,7 +16,6 @@ jobs:
|
|
|
16
16
|
python-version: '3.12'
|
|
17
17
|
# This job times the `bpl` that releases ship, which `cargo stage --profile dist` builds, as the `Release` workflow does. `bpl --costs` prints every case's cost and budget for `basedpl.bpltests.logged_costs`. A case over its budget doesn't fail it. The job builds without a cache. The `Test` workflow's caches hold only test-profile builds.
|
|
18
18
|
- run: pip install 'fastws-cli>=0.0.25' && cargo stage --profile dist
|
|
19
|
-
- run: grep -m1 'model name' /proc/cpuinfo
|
|
20
19
|
- run: wheel/scripts/bpl --costs tests/speed.txt
|
|
21
20
|
# `bpl-tests thread_scaling` times a large product, a 512×512 SVD and Hermitian eigendecomposition, a stack of QR decompositions and a large elementwise function on one thread and on the runner's cores, and fails when one speeds up less than 1.5 times. Speed budgets are single-thread measurements, so this is the check of multithreaded speed. It comes last, so that a failure here still leaves every cost logged.
|
|
22
21
|
- run: pip install -e . && bpl-tests thread_scaling --bpl wheel/scripts/bpl
|
|
@@ -33,5 +32,4 @@ jobs:
|
|
|
33
32
|
with:
|
|
34
33
|
shared-key: wasm
|
|
35
34
|
- run: cargo wasm --strip
|
|
36
|
-
- run: grep -m1 'model name' /proc/cpuinfo
|
|
37
35
|
- run: BASEDPL_COSTS=1 node --no-liftoff tests/wasm-speed.mjs
|
|
@@ -23,7 +23,6 @@ jobs:
|
|
|
23
23
|
- uses: Swatinem/rust-cache@v2
|
|
24
24
|
with:
|
|
25
25
|
shared-key: native
|
|
26
|
-
- run: grep -m1 'model name' /proc/cpuinfo
|
|
27
26
|
# With `BASEDPL_COSTS`, the speed tests here and in the `wasm` job log every case's cost, which `basedpl.bpltests.logged_costs` reads.
|
|
28
27
|
- run: BASEDPL_COSTS=1 cargo test
|
|
29
28
|
- uses: actions/setup-python@v7
|
|
@@ -49,5 +48,4 @@ jobs:
|
|
|
49
48
|
shared-key: wasm
|
|
50
49
|
- run: cargo wasm --strip
|
|
51
50
|
- run: node --test tests/wasm.mjs
|
|
52
|
-
- run: grep -m1 'model name' /proc/cpuinfo
|
|
53
51
|
- run: BASEDPL_COSTS=1 node --no-liftoff --test tests/wasm-speed.mjs
|
|
@@ -93,7 +93,7 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
|
|
93
93
|
|
|
94
94
|
[[package]]
|
|
95
95
|
name = "basedpl"
|
|
96
|
-
version = "0.1.
|
|
96
|
+
version = "0.1.71"
|
|
97
97
|
dependencies = [
|
|
98
98
|
"base64 0.23.1",
|
|
99
99
|
"bytemuck",
|
|
@@ -137,7 +137,7 @@ dependencies = [
|
|
|
137
137
|
|
|
138
138
|
[[package]]
|
|
139
139
|
name = "basedpl-cli"
|
|
140
|
-
version = "0.1.
|
|
140
|
+
version = "0.1.71"
|
|
141
141
|
dependencies = [
|
|
142
142
|
"async-trait",
|
|
143
143
|
"basedpl",
|
|
@@ -150,7 +150,7 @@ dependencies = [
|
|
|
150
150
|
|
|
151
151
|
[[package]]
|
|
152
152
|
name = "basedpl-py"
|
|
153
|
-
version = "0.1.
|
|
153
|
+
version = "0.1.71"
|
|
154
154
|
dependencies = [
|
|
155
155
|
"basedpl",
|
|
156
156
|
"num-bigint",
|
|
@@ -165,7 +165,7 @@ dependencies = [
|
|
|
165
165
|
|
|
166
166
|
[[package]]
|
|
167
167
|
name = "basedpl-wasm"
|
|
168
|
-
version = "0.1.
|
|
168
|
+
version = "0.1.71"
|
|
169
169
|
dependencies = [
|
|
170
170
|
"basedpl",
|
|
171
171
|
"js-sys",
|
|
@@ -1413,9 +1413,9 @@ dependencies = [
|
|
|
1413
1413
|
|
|
1414
1414
|
[[package]]
|
|
1415
1415
|
name = "rustymath"
|
|
1416
|
-
version = "0.1.
|
|
1416
|
+
version = "0.1.45"
|
|
1417
1417
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1418
|
-
checksum = "
|
|
1418
|
+
checksum = "539dbd536cc6c64e9080a2c0bc346b6cf2f5308f0e29f814bfefd244bce87d07"
|
|
1419
1419
|
dependencies = [
|
|
1420
1420
|
"foldhash",
|
|
1421
1421
|
"half",
|
|
@@ -1434,9 +1434,9 @@ dependencies = [
|
|
|
1434
1434
|
|
|
1435
1435
|
[[package]]
|
|
1436
1436
|
name = "rustymath-buffer"
|
|
1437
|
-
version = "0.1.
|
|
1437
|
+
version = "0.1.45"
|
|
1438
1438
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1439
|
-
checksum = "
|
|
1439
|
+
checksum = "1b3f549d927a69037ffe1ec3d11a318d89bbb115ac26c071d19150207e62a811"
|
|
1440
1440
|
dependencies = [
|
|
1441
1441
|
"pyo3",
|
|
1442
1442
|
"rustymath",
|
|
@@ -2086,7 +2086,7 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
|
|
2086
2086
|
|
|
2087
2087
|
[[package]]
|
|
2088
2088
|
name = "xtask"
|
|
2089
|
-
version = "0.1.
|
|
2089
|
+
version = "0.1.71"
|
|
2090
2090
|
dependencies = [
|
|
2091
2091
|
"anyhow",
|
|
2092
2092
|
"rustymath",
|
|
@@ -5,14 +5,14 @@ members = ["py"]
|
|
|
5
5
|
join_comments = true
|
|
6
6
|
|
|
7
7
|
[workspace.package]
|
|
8
|
-
version = "0.1.
|
|
8
|
+
version = "0.1.71"
|
|
9
9
|
edition = "2024"
|
|
10
10
|
rust-version = "1.98"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
|
|
13
13
|
[workspace.dependencies]
|
|
14
|
-
rustymath = { version = ">=0.1.
|
|
15
|
-
rustymath-buffer = ">=0.1.
|
|
14
|
+
rustymath = { version = ">=0.1.44, <1", default-features = false }
|
|
15
|
+
rustymath-buffer = ">=0.1.44, <1"
|
|
16
16
|
pyo3 = ">=0.29.2, <1"
|
|
17
17
|
pyo3-build-config = ">=0.29.2, <1"
|
|
18
18
|
|
|
@@ -26,19 +26,19 @@ When another agent's unfinished edits stop the shared tree compiling, run `cargo
|
|
|
26
26
|
|
|
27
27
|
An already-running Python process retains its loaded extension. In a kernel, use `from basedpl.worker import fresh; fresh(code)` to check the installed `bpl` in a fresh worker instead of restarting the kernel. A BPL error raises `BplError`. `fresh(code, bpl=PATH)` runs a saved copy of `bpl`, such as the baseline described below. Check one expression and read its result before building on it. `basedpl.bpltests.check_file` and `check_page` check reference files and documentation through the worker without building Rust. With `bpl=PATH`, they check a saved copy without installing it.
|
|
28
28
|
|
|
29
|
-
Measure with `BASEDPL_COSTS=1 cargo test --test speed` or `basedpl.bpltests.time_cases(cases)`. Both count costs in copies. A copy is the time to write as many floats as the data has items, each negated, into a second buffer
|
|
29
|
+
Measure with `BASEDPL_COSTS=1 cargo test --test speed` or `basedpl.bpltests.time_cases(cases)`. Both count costs in copies. A copy is the time to write as many floats as the data has items, each negated, into a second buffer. `rustymath::timing::COPIES` stores the copy time of each machine that runs the speed tests, by its CPU, the item's size and the number of items, at the slower end of what that copy normally takes, for copies whose source and destination fit in cache, 64 MiB together (`CACHED_BYTES`). A copy timed beside each case varied more than 3 times on CI's runner, where the functions' own times didn't, so stored times keep that noise out of every cost. A larger copy runs at the speed of memory, which a shared runner varies by up to 2 times between runs, and the cases of that size vary with it. The test measures such a copy once in each run. It also measures a cached size with no entry once, and prints a line to add to `COPIES`. The browser build has entries of its own, for its copy under the JavaScript engine. It can't read the CPU's name, and `tests/wasm-pkg.mjs` passes it the name that Node reads. Each case's line in a log ends with its time for each item, which `logged_costs` keeps in each row, and the log has a copy line for each size of data, which it reads into its result's `copies`. Budgets are single-thread measurements. `thread_scaling()` checks multithreaded speed: it times a large product, a 512×512 SVD and Hermitian eigendecomposition, a stack of QR decompositions and a large elementwise function on one thread and on the machine's cores, and fails when one speeds up less than 1.5 times. Accelerate's GESDD and SYEVD run on one thread whatever its threading setting. On macOS, where Accelerate computes the product, the SVD and the Hermitian eigendecomposition, those three cases need only the speedups in `_ACCELERATED_LEAST`. The manual `Speed` workflow runs it. `speed_costs()` reads the cases in `tests/speed.txt`. `cases=` selects expressions, `wasm=DIR` selects a browser build, `bpl=PATH` selects another native `bpl`, such as a saved copy, and `native=True` times rustymath's own linear algebra, which dev builds compile in. `logged_costs(log)` reads CI output from its text or a file. `cargo_costs(path)` runs either project's Rust speed test, and `pytest_costs(path)` runs rustymath's Python speed test.
|
|
30
30
|
|
|
31
|
-
Costs in copies can't show a per-call change of a few percent, because code layout alone moves a timing by more than that. `instructions(cases)` counts instructions, which load and layout don't change. Each case is
|
|
31
|
+
Costs in copies can't show a per-call change of a few percent, because code layout alone moves a timing by more than that. `instructions(cases)` counts instructions, which load and layout don't change. Each case is an expression, such as `k(⌈/⊢)⌸a`, run once per step inside `{case ⋄ ⍵}⍣N⊢0`. That dfn's own call adds about 1,900 instructions a step. A case that names `N` makes its own calls instead, such as `{⍵}⍣N⊢0`, which makes one dfn call a step. Such a case must make one call per step: a map such as `f¨N⍴0` calls a dfn once on all its cells, so it gives no per-call cost. It runs fresh single-threaded `bpl` processes at four values of `N`. For each pair of successive values, it divides the difference in instructions retired by the difference in `N`, which cancels startup and `setup=`, and it reports the median of those three slopes. The median drops a one-off cost that starts once `N` passes some size, because such a cost falls between only one pair. `≢•ucs N⍴"ab" "cde"` has one: it retires about 5×10⁶ more instructions from about 131,350 items on. Every value of `N` lies past the warm-up, from a rough count at 1,000 steps, because a case that allocates an array at each step retires extra instructions over its first steps while the allocator settles. Each process pays a different warm-up, up to about 5×10⁶ instructions, so successive values of `N` lie 5×10⁸ instructions apart, where that difference moves a slope by under 1%. `counts=` sets two or more values instead. It reads `/usr/bin/time -l` on macOS and `perf stat` on Linux, and `bpl=PATH` counts a saved copy. A case whose result stays pending, such as large windows or a transpose, counts making the pending copy, not the copy: `3↕v` retires about 12,000 instructions a step. `force=True` computes each step's result in full, through an indexed write, for any result of rank 1 or more: `3↕v` then counts the copy itself, about 9 million instructions a step. A join computes at once and needs no `force`.
|
|
32
32
|
|
|
33
33
|
`tests/instructions.txt` holds the instructions that each case retires for each step, as counted on macOS on Apple silicon. Most cases make one call per step, from `{⍵}⍣N⊢0` to `fib`. Three map a function over the same 64 cells each step, through Each, Rank's general path and a system function's own item map. `instruction_budgets()` counts each case through `instructions` and marks a case `over` when it retires more than 3% above its count, and `stale` when it retires more than 3% below it. `tests/test_instructions.py` fails on either mark, and skips on other platforms. The change that makes a call cheaper also lowers each count it moves. A new compiler version can inline differently and move the counts, and then they're set again from `bpl-tests instruction_budgets`. CI checks only the speed budgets.
|
|
34
34
|
|
|
35
|
-
`profile(case)` shows where a case's instructions go, by function, on macOS. A fresh process repeats the case on one thread while `sample` counts how often each function is at the top of the main thread's stack. A function's share of those samples, times the build's count from `instructions`, estimates its instructions per step. The estimate assumes every function retires instructions at the same rate. When a count moves, `profile_diff(case, base)` shows which functions moved it, from those estimates for both builds. A function that one build inlines and the other calls out of line appears under one build alone. Run them before reading machine code. `base` is a saved copy, and `bpl=PATH` sets the build to profile, the installed one by default.
|
|
35
|
+
`profile(case)` shows where a case's instructions go, by function, on macOS. It takes a case, and `force=`, as `instructions` does. A fresh process repeats the case on one thread while `sample` counts how often each function is at the top of the main thread's stack. A function's share of those samples, times the build's count from `instructions`, estimates its instructions per step. The estimate assumes every function retires instructions at the same rate. When a count moves, `profile_diff(case, base)` shows which functions moved it, from those estimates for both builds. A function that one build inlines and the other calls out of line appears under one build alone. Run them before reading machine code. `base` is a saved copy, and `bpl=PATH` sets the build to profile, the installed one by default.
|
|
36
36
|
|
|
37
37
|
`bpl-tests NAME ARGUMENT...` runs one of these functions from the shell and prints its result: `check_file`, `check_page`, `time_cases`, `instructions`, `instruction_budgets`, `profile`, `profile_diff`, `speed_costs`, `thread_scaling`, `cargo_costs`, `pytest_costs`, `compare_costs` or `wasm_test`. Run long checks and timings this way, not in the shared kernel, which runs one cell at a time for every agent. For example, `bpl-tests time_cases '+/v' --threads 4` times `+/v` on four threads. `time_cases`, `instructions` and `speed_costs` take cases as positional arguments. When a case starts with `-`, put the cases last, after `--`, as in `bpl-tests speed_costs -- '-.×m' '+/v'`. `bpl-tests NAME -h` lists a function's arguments.
|
|
38
38
|
|
|
39
|
-
On each push, the `Test` workflow logs every native cost in its `test` job and every wasm cost in its `wasm` job. The speed test that writes them times the test-profile build that the budgets limit. `bpl --costs FILE` times the cases in a file in `speed.txt`'s format as the speed test does, and prints each cost with this machine's budget, without failing on a budget. The manual `Speed` workflow runs it on a `dist` build, the profile releases use, on CI's runner, beside its wasm timing. The `Release` workflow runs it on each binary it builds, on GitHub's runners. Their logs show what releases cost, for comparison with the `Test` workflow's. `logged_costs` reads all three workflows' logs. When you review a run, look for costs over their budgets and for costs that changed by more than 20% since the run before. `compare_costs(before, after)` lists both, from two runs' logs or from the rows that `speed_costs` and the other timing functions give.
|
|
39
|
+
On each push, the `Test` workflow logs every native cost in its `test` job and every wasm cost in its `wasm` job. The speed test that writes them times the test-profile build that the budgets limit. `bpl --costs FILE` times the cases in a file in `speed.txt`'s format as the speed test does, and prints each cost with this machine's budget, without failing on a budget. The manual `Speed` workflow runs it on a `dist` build, the profile releases use, on CI's runner, beside its wasm timing. The `Release` workflow runs it on each binary it builds, on GitHub's runners. Their logs show what releases cost, for comparison with the `Test` workflow's. `logged_costs` reads all three workflows' logs. When you review a run, look for costs over their budgets and for costs that changed by more than 20% since the run before. `compare_costs(before, after)` lists both, from two runs' logs or from the rows that `speed_costs` and the other timing functions give. It measures each change on times per item where both runs give them, so a change to `COPIES` doesn't show as one. Every log names its CPU in its copy lines. Study a slowdown until you know its cause only when it appears in more than one CI run, or when two or more similar cases slow by similar amounts. Treat any other slowdown as normal variation.
|
|
40
40
|
|
|
41
|
-
An initial budget is twice what a first-principles analysis says the case should cost, as each budget file's header says. A later budget comes from a fuller understanding of the case than that analysis gives, such as a platform difference or a cost the analysis missed, and the case's comment records it. A case over its budget needs study, which finds either a speed bug to fix or something the analysis missed. Avoid raising a budget to match a run without understanding why the case costs what it does. Each case line in a budget file holds a native budget for macOS on Apple silicon, then one for Linux on CI's x86 runner, then, in basedpl's file, a wasm budget. One expected cost serves every platform at first, until a specific difference on one platform is found. macOS running matrix products and decompositions on Accelerate, where CI runs rustymath's own kernels, is such a difference. A comment above each case, or above its group, gives the expected cost with its reason, and any platform's own cost with the reason for it. `set_budgets(expected)` writes the budgets for the cases that `expected` names, a dict from each case's code to its expected cost. `builds=` limits the change to one platform's column, for a cost that differs there. A case can appear in more than one section, such as `[copies]` and `[widths]`. `sections=` limits the change to the sections it names. `path=` selects one of rustymath's budget files, `tests/speed.txt` or `tests/speed_python.txt`.
|
|
41
|
+
An initial budget is twice what a first-principles analysis says the case should cost, as each budget file's header says. A later budget comes from a fuller understanding of the case than that analysis gives, such as a platform difference or a cost the analysis missed, and the case's comment records it. A case over its budget needs study, which finds either a speed bug to fix or something the analysis missed. Avoid raising a budget to match a run without understanding why the case costs what it does. Each case line in a budget file holds a native budget for macOS on Apple silicon, then one for Linux on CI's x86 runner, then, in basedpl's file, a wasm budget for Node on CI's x86 runner. One expected cost serves every platform at first, until a specific difference on one platform is found. macOS running matrix products and decompositions on Accelerate, where CI runs rustymath's own kernels, is such a difference. A comment above each case, or above its group, gives the expected cost with its reason, and any platform's own cost with the reason for it. `set_budgets(expected)` writes the budgets for the cases that `expected` names, a dict from each case's code to its expected cost. `builds=` limits the change to one platform's column, for a cost that differs there. A case can appear in more than one section, such as `[copies]` and `[widths]`. `sections=` limits the change to the sections it names. `path=` selects one of rustymath's budget files, `tests/speed.txt` or `tests/speed_python.txt`.
|
|
42
42
|
|
|
43
43
|
`tests/explain.rs`'s `speed_cases_take_their_routes` runs each speed case once more, after a warm-up run, as `•explain` runs its text (`timing::routes`). It fails on any slow-path event that the case's lines don't name: a pending array that a fused group computed on its own (`stored`), a call that computed pending items (`forced`), a batched map's fallback (`fallbacks`), a write that left an array mixed (`mixed`), or a route that handled items one at a time (`itemwise`). A line above a case names one event, as `•explain`'s record names it: the function for `forced`, the reason for `stored` and `fallbacks`, the code for `mixed` and the route for `itemwise`. A faster route that code considered and didn't take is a decline (`declined`), named by its route and reason, as in `fusion: an exact argument`. A case needn't name a decline, because each one explains another event, but a `gap` line can name one to record why the fast route was refused. Code in either repository reports a decline through `rustymath::events::declined`, which costs one atomic load unless `•explain` runs. `# allows RECORD "EVENT": principle` names an event that a principle allows, such as a matrix product reading its arguments whole. `# gap RECORD "EVENT": what should happen` names an event that a faster route should remove, and the test prints these gaps, which `cargo test -- --nocapture` shows. Any other event is a route to study, as a case over its budget is: add an `allows` line only for a principle, and a `gap` line once the study finds what should happen. The test also fails on a line whose event no longer appears, so a fixed gap's line goes.
|
|
44
44
|
|
|
@@ -48,7 +48,7 @@ For a change meant to make something faster, or a change to code that every call
|
|
|
48
48
|
|
|
49
49
|
Cooperating tools share the machine's timing lock, `/tmp/aai-ws-timing.lock`. Timings take it exclusively. `cargo develop`, `cargo wait`, `cargo wasm`, the reference test, pytest, `check_file`, `check_page`, `bpl-nb` and `wasm_test` without `speed` take it shared. Rust code takes it through `rustymath::timing`. Python code takes it with `fastws.cargo.timing_lock`. Node can't lock a file. `wasm_test` and `speed_costs(wasm=DIR)` hold the lock in Python while Node runs. Other Rust tests and bare Cargo commands do not all take it. After five minutes a timing fails. A build or test run then goes ahead without the lock. After 30 seconds, an exclusive wait prints the processes with the lock file open. The list can include processes that are only waiting for the lock. Never take the lock yourself in the shared kernel process. Never coordinate timings by hand. A hold that outlasts its call blocks the timings that later calls launch. Each such call stalls every other agent's kernel calls while it waits. Native timings run on one thread and request user-interactive macOS QoS, which favours the performance cores. QoS is not CPU affinity.
|
|
50
50
|
|
|
51
|
-
Run `cargo wasm` during development only for browser work. Agents use `cargo wasm --out DIR`, then run the browser tests against that build with `basedpl.bpltests.wasm_test(DIR)` for `tests/wasm.mjs` and `wasm_test(DIR, speed=True)` for `tests/wasm-speed.mjs`. This avoids replacing a module another agent is using. For other edits under `cfg(web)` or `host_web.rs`, check compilation with `cargo cs -p basedpl-wasm --target wasm32-unknown-unknown`, which checks the browser wrapper crate together with basedpl's browser code. `cargo cs` alone compiles neither.
|
|
51
|
+
Run `cargo wasm` during development only for browser work. Agents use `cargo wasm --out DIR`, then run the browser tests against that build with `basedpl.bpltests.wasm_test(DIR)` for `tests/wasm.mjs` and `wasm_test(DIR, speed=True)` for `tests/wasm-speed.mjs`. That speed test fails on a wasm budget only on Linux x86-64, CI's platform. On any other host it prints every cost and marks each case still over its budget after the retries, as `bpl --costs` does. This avoids replacing a module another agent is using. For other edits under `cfg(web)` or `host_web.rs`, check compilation with `cargo cs -p basedpl-wasm --target wasm32-unknown-unknown`, which checks the browser wrapper crate together with basedpl's browser code. `cargo cs` alone compiles neither.
|
|
52
52
|
|
|
53
53
|
### X86-64 machine code
|
|
54
54
|
|
|
@@ -184,7 +184,7 @@ Bare numbers, empty literals and NaN use the parsed float width. Top-level `eval
|
|
|
184
184
|
|
|
185
185
|
Float operations compute at the wider operand width. Exact operands round to the width an operation computes at. f16 operations run on f32 copies. `Numeric::halving` decides when: for a call with an f16 operand, or any call while the default width is 16 bits. `pervasive::halves` applies it to whole operations and `Number::halves` to scalar arithmetic and comparisons. `fusion::defer` declines those calls. Exact inputs convert once, to f32, and float outputs round back to f16. The scalar `Number` path rounds each f16 step. Complex parts have f32/f64 widths; f16 becoming complex uses f32 parts. Scalar Complex32 arithmetic computes at Complex64 and rounds, while kernels can compute at f32. These paths need not agree in their last bits.
|
|
186
186
|
|
|
187
|
-
Floats use IEEE NaN, infinity and signed zero. Comparison treats NaN as unequal; match/search treat it as one value, and grade puts it last. Exact arithmetic involving division by zero can produce those float values. Floor/ceiling retain an exact operand beside infinity and give NaN
|
|
187
|
+
Floats use IEEE NaN, infinity and signed zero. Comparison treats NaN as unequal; match/search treat it as one value, and grade puts it last. Exact arithmetic involving division by zero can produce those float values. Floor/ceiling retain an exact operand beside infinity, and beside NaN give the right argument, as a float. Their folds, scans and products start at the identity and put each item on the left, so a NaN item changes no running result. Monadic floor/ceiling/sign turn whole finite results into exact integers, including big integers; nonfinite results remain floats. Use `Number::float(value, width)` and explicit-width array construction outside `number.rs` instead of implicit f64 conversion.
|
|
188
188
|
|
|
189
189
|
Under `•prefs ["kinds":"static"]`, a result's kind follows from its arguments' kinds alone, as `meta/kinds.md` describes. `Numeric` carries `kinds` and `int`. `pervasive::map` chooses a dyadic call's kind through `joined`, which applies rustymath's `DType::common` after `Numeric::kind`. Atoms promote as arrays do. Python's API makes its scalar arguments weak through `Value::weakened`. The loops keep the failed results that `keeps` names: floats' NaNs, and the wrapped integers of objects that report `WRAPS`. The kernel tables choose float `⌊ ⌈ ×` and wrapping negation from `run.numeric()`. `Stored::build_at` keeps complex results complex. `Primitive::number` gives every pervasive result on numbers, as well as each step of the fold of numbers, and `statically` adjusts it. `Number::parse` keeps complex literals and checks integer literals against the `int` width.
|
|
190
190
|
|
|
@@ -15,11 +15,12 @@ Complex floor uses APL's Gaussian-integer rule.
|
|
|
15
15
|
⌊1.5j0.5 ⍝ 1j1
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`X⌊Y` takes the real minimum, and for characters the one with the lower code point. A number is less than any character, as [`⍋`](delta-stile.qmd) orders them. Pervasive. Beside NaN
|
|
18
|
+
`X⌊Y` takes the real minimum, and for characters the one with the lower code point. A number is less than any character, as [`⍋`](delta-stile.qmd) orders them. Pervasive. Beside NaN it gives `Y`: `$n⌊3` is `3`, and `3⌊$n` is `$n`. A reduction or scan starts at `∞` and puts each item on the left, so it leaves NaN items out: `⌊/$n 1` is `1`, and `⌊/$n $n` is `∞`. A NaN seed stays NaN. Empty reduction identity: `∞`.
|
|
19
19
|
|
|
20
20
|
```bpl
|
|
21
21
|
3⌊1 5 ⍝ 1 3
|
|
22
22
|
3⌊1 $n 5 ⍝ 1 $n 3
|
|
23
|
+
⌊\1 $n 3 ⍝ 1 1 1
|
|
23
24
|
"abc"⌊"bab" ⍝ "aab"
|
|
24
25
|
3⌊'a' ⍝ 3
|
|
25
26
|
```
|
|
@@ -9,11 +9,12 @@ Function. Keys: `Alt-s`. Hanzi: `上`. Ranks: `0` monadic, `0 0` dyadic
|
|
|
9
9
|
⌈3r2 ⍝ 2ₓ
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
`X⌈Y` takes the real maximum, and for characters the one with the higher code point. A character is greater than any number, as [`⍋`](delta-stile.qmd) orders them. Pervasive. Beside NaN
|
|
12
|
+
`X⌈Y` takes the real maximum, and for characters the one with the higher code point. A character is greater than any number, as [`⍋`](delta-stile.qmd) orders them. Pervasive. Beside NaN it gives `Y`: `$n⌈3` is `3`, and `3⌈$n` is `$n`. A reduction or scan starts at `¯∞` and puts each item on the left, so it leaves NaN items out: `⌈/$n 1` is `1`, and `⌈/$n $n` is `¯∞`. A NaN seed stays NaN. Empty reduction identity: `¯∞`.
|
|
13
13
|
|
|
14
14
|
```bpl
|
|
15
15
|
3⌈1 5 ⍝ 3 5
|
|
16
16
|
3⌈1 $n 5 ⍝ 3 $n 5
|
|
17
|
+
⌈\1 $n 3 ⍝ 1 1 3
|
|
17
18
|
"abc"⌈"bab" ⍝ "bbc"
|
|
18
19
|
3⌈'a' ⍝ 'a'
|
|
19
20
|
```
|
|
@@ -14,7 +14,7 @@ Each entry below names a difference from Dyalog APL, gives a short example, and
|
|
|
14
14
|
|
|
15
15
|
## Language
|
|
16
16
|
|
|
17
|
-
- [Syntax](syntax.html.md): spaces divide a statement into runs, each read as one parenthesised group: `a+b × c+d` is `(a+b)(×)(c+d)`. Digits in a name need `_`, as in `x_1`. `x1` is `x` applied to `1`.
|
|
17
|
+
- [Syntax](syntax.html.md): spaces divide a statement into runs, each read as one parenthesised group: `a+b × c+d` is `(a+b)(×)(c+d)`. A short run beside spaced runs is a group of its own: `1+k (⌈/⊢)⌸ a` is `(1+k)((⌈/⊢)⌸)(a)`. To keep code together, remove its spaces, as in `1+k(⌈/⊢)⌸a`, rather than wrapping it in parentheses. For items of a list, put them in brackets with `;` between them: `[+/x ; +/x ÷ ≢x]`. Digits in a name need `_`, as in `x_1`. `x1` is `x` applied to `1`.
|
|
18
18
|
- [Evaluation](evaluation.html.md): a run that ends in a function is a train. `+/÷≢ x` is the mean of `x`, while `+/÷≢x` reads as `+/÷(≢x)`.
|
|
19
19
|
- [Agreement](evaluation.html.md#agreement-and-pervasion): pervasive functions, Each and Rank align leading axes: `[1 2 3⋄4 5 6]+10 20` is `[11 12 13⋄24 25 26]`. An axis of length 1 expands.
|
|
20
20
|
- [Maps](evaluation.html.md#functions-and-effects): Each, Rank, stencils and outer products call a dfn once on all their cells where they can: `{⍵×⍵+1}¨v` costs about what `v×v+1` costs. A predicate on a cell's value runs each body once, on the cells that take it. Output or a nested result makes them call the dfn once for each cell. A fold or scan by a dfn along each row of a matrix calls it once for each column, on every row at once. `f.g` calls a dfn `g` once, on all its pairings.
|
|
@@ -314,14 +314,23 @@ def _program(code, n, setup):
|
|
|
314
314
|
# The instructions that a case retires before the lowest value of `N` that `instructions` picks, and between successive values. A case that allocates an array at each step retires a few tens of millions more instructions over its first steps, while the allocator settles. `{↑[⍵ 1]}⍣N⊢0` counts the same from 3×10⁴ steps on, about 2×10⁸ instructions, and `{fib 10}⍣N⊢0` from 30 steps on. Each process pays a different warm-up, up to about 5×10⁶ instructions, which moves a slope over 5×10⁸ instructions by under 1%. Some cases also pay a one-off cost once `N` passes a size: `≢•ucs N⍴"ab" "cde"` retires about 5×10⁶ more instructions from about 131,350 items on.
|
|
315
315
|
_SETTLE, _SPAN = 5e8, 5e8
|
|
316
316
|
|
|
317
|
+
# A case that names `N` outside its strings says itself how it repeats. `_stepped` runs any other case once per step.
|
|
318
|
+
_NAMES_N, _QUOTED = re.compile(r'(?<!\w)N(?!\w)'), re.compile(r'"(?:[^"]|"")*"|\'.\'')
|
|
319
|
+
|
|
320
|
+
def _stepped(case, force=False):
|
|
321
|
+
"BPL that makes one call of `case` per step of `N`. A case that names `N`, such as `{⍵}⍣N⊢0`, is that BPL already. Any other case runs once per step, and its result is dropped, or with `force` computed in full through an indexed write. `force` needs a result of rank 1 or more."
|
|
322
|
+
if _NAMES_N.search(_QUOTED.sub('', case)): return case
|
|
323
|
+
return f'{{x←{case} ⋄ x.[0]←x.[0] ⋄ ⍵}}⍣N⊢0' if force else f'{{{case} ⋄ ⍵}}⍣N⊢0'
|
|
324
|
+
|
|
317
325
|
def instructions(
|
|
318
|
-
cases:Annotated[str, {'nargs':'+'}], # A BPL expression that makes
|
|
326
|
+
cases:Annotated[str, {'nargs':'+'}], # A BPL expression to run once per step, such as `k(⌈/⊢)⌸a`, or one that names `N` and makes its own call per step of it, such as `{⍵}⍣N⊢0`, or a list of them
|
|
319
327
|
setup='', # BPL that defines what the cases use, run before each case
|
|
328
|
+
force=False, # Compute each step's result in full where it would stay pending, such as large windows or a transpose, for the cases that don't name `N`
|
|
320
329
|
counts:Annotated[int, {'nargs':'+'}]=None, # Two or more values of `N`; None: four values past the allocator's warm-up, from a rough count at 1,000 steps
|
|
321
330
|
runs=3, # Processes at each value of `N`, of which the lowest count is used
|
|
322
331
|
bpl:Path=None # A native `bpl` to count, such as a copy saved before a change; None: the installed one
|
|
323
332
|
):
|
|
324
|
-
"The instructions that each case retires per unit of `N`, on one thread. For each pair of successive values of `N`, the difference between fresh `bpl` processes, divided by the difference in `N`, cancels startup and `setup`. The result is the median of those slopes. The median drops a one-off cost that starts once `N` passes some size, because such a cost falls between only one pair. Unlike timings, instruction counts don't vary with load or code layout, so they show per-call changes of a few percent. A case must make one call per step of `N`, as `⍣` does: a map such as `f¨N⍴0` calls a dfn once on all its cells, so it gives no per-call cost."
|
|
333
|
+
"The instructions that each case retires per unit of `N`, on one thread. A case that doesn't name `N` runs once per step inside `{case ⋄ ⍵}⍣N⊢0`. That dfn's own call adds about 1,900 instructions per step. For each pair of successive values of `N`, the difference between fresh `bpl` processes, divided by the difference in `N`, cancels startup and `setup`. The result is the median of those slopes. The median drops a one-off cost that starts once `N` passes some size, because such a cost falls between only one pair. Unlike timings, instruction counts don't vary with load or code layout, so they show per-call changes of a few percent. A case that names `N` must make one call per step of `N`, as `⍣` does: a map such as `f¨N⍴0` calls a dfn once on all its cells, so it gives no per-call cost."
|
|
325
334
|
cases = [cases] if isinstance(cases, str) else list(cases)
|
|
326
335
|
def count(code, n, runs=runs): return min(_retired(_program(code, n, setup), bpl) for _ in range(runs))
|
|
327
336
|
def steps(code):
|
|
@@ -331,8 +340,8 @@ def instructions(
|
|
|
331
340
|
def per_step(code):
|
|
332
341
|
ns = steps(code)
|
|
333
342
|
cs = [count(code, n) for n in ns]
|
|
334
|
-
return
|
|
335
|
-
return Instructions(per_step(
|
|
343
|
+
return statistics.median((c1-c0)/(n1-n0) for n0, n1, c0, c1 in zip(ns, ns[1:], cs, cs[1:]))
|
|
344
|
+
return Instructions((case, per_step(_stepped(case, force))) for case in cases)
|
|
336
345
|
|
|
337
346
|
class ProfileDiff(list):
|
|
338
347
|
"Each function's estimated instructions per step of `N` on two builds, as `(function, before, after)` rows, largest change first, below a row of each build's total."
|
|
@@ -372,11 +381,12 @@ def _self_samples(code, bpl, seconds):
|
|
|
372
381
|
for name, n in zip(subprocess.run(['c++filt'], input='\n'.join(selfs), capture_output=True, text=True).stdout.splitlines(), selfs.values()): named[name] += n
|
|
373
382
|
return named, total
|
|
374
383
|
|
|
375
|
-
def _estimates(case, setup, bpl, seconds):
|
|
384
|
+
def _estimates(case, setup, bpl, seconds, force):
|
|
376
385
|
"Each function's estimated instructions per step of `N` for `case` on `bpl`, and the build's instructions per step, as `profile` describes."
|
|
377
386
|
if sys.platform != 'darwin': raise ValueError(f'no sampling profiler on {sys.platform}')
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
stepped = _stepped(case, force)
|
|
388
|
+
[(_, per_step)] = instructions(stepped, setup, bpl=bpl)
|
|
389
|
+
samples, total = _self_samples(_program(f'{{{stepped} ⋄ ⍵}}⍣1000000000⊢0', max(1, round(1e8/per_step)), setup), bpl, seconds)
|
|
380
390
|
return {f: per_step*n/total for f, n in samples.items()}, per_step
|
|
381
391
|
|
|
382
392
|
class Profile(list):
|
|
@@ -387,26 +397,28 @@ class Profile(list):
|
|
|
387
397
|
def __repr__(self): return '\n'.join(f'{n:>10,.0f} {n/self.total:>6.1%} {f[:150]}' for f, n in [('total', self.total), *self])
|
|
388
398
|
|
|
389
399
|
def profile(
|
|
390
|
-
case, # A BPL expression
|
|
400
|
+
case, # A BPL expression, as `instructions` takes it, such as `k(⌈/⊢)⌸a` or `{⍵}⍣N⊢0`
|
|
391
401
|
setup='', # BPL that defines what the case uses, run before it
|
|
402
|
+
force=False, # Compute each step's result in full, as `instructions`' `force` does
|
|
392
403
|
bpl:Path=None, # A native `bpl` to profile, such as a copy saved before a change; None: the installed one
|
|
393
404
|
seconds=3, # How long macOS `sample` watches the build
|
|
394
405
|
rows=20 # How many functions to show
|
|
395
406
|
):
|
|
396
407
|
"Where `case`'s instructions per step of `N` go, by function, on macOS. A fresh process repeats the case on one thread while `sample` counts how often each function is at the top of the main thread's stack. A function's estimate is its share of those samples times the build's instructions per step from `instructions`. The shares come from time, so the estimates assume every function retires instructions at the same rate."
|
|
397
|
-
estimates, total = _estimates(case, setup, bpl, seconds)
|
|
408
|
+
estimates, total = _estimates(case, setup, bpl, seconds, force)
|
|
398
409
|
return Profile(sorted(estimates.items(), key=lambda r: -r[1])[:rows], total)
|
|
399
410
|
|
|
400
411
|
def profile_diff(
|
|
401
|
-
case, # A BPL expression
|
|
412
|
+
case, # A BPL expression, as `instructions` takes it, such as `k(⌈/⊢)⌸a` or `{⍵}⍣N⊢0`
|
|
402
413
|
base:Path, # A native `bpl` to compare with, such as a copy saved before a change
|
|
403
414
|
setup='', # BPL that defines what the case uses, run before it
|
|
415
|
+
force=False, # Compute each step's result in full, as `instructions`' `force` does
|
|
404
416
|
bpl:Path=None, # A native `bpl` to compare with `base`; None: the installed one
|
|
405
417
|
seconds=3, # How long macOS `sample` watches each build
|
|
406
418
|
rows=20 # How many functions to show
|
|
407
419
|
):
|
|
408
420
|
"Where `case`'s instructions per step of `N` moved between two builds, by function, on macOS, from `profile`'s estimates for each build. A function that only one build calls out of line, such as one that stopped being inlined, appears in that build alone."
|
|
409
|
-
(before, tb), (after, ta) = _estimates(case, setup, base, seconds), _estimates(case, setup, bpl, seconds)
|
|
421
|
+
(before, tb), (after, ta) = _estimates(case, setup, base, seconds, force), _estimates(case, setup, bpl, seconds, force)
|
|
410
422
|
fs = sorted(before.keys() | after.keys(), key=lambda f: -abs(after.get(f, 0) - before.get(f, 0)))
|
|
411
423
|
return ProfileDiff([(f, before.get(f, 0), after.get(f, 0)) for f in fs[:rows]], (tb, ta))
|
|
412
424
|
|
|
@@ -449,11 +461,11 @@ def _or_dash(x, spec): return '-' if x is None else format(x, spec)
|
|
|
449
461
|
def _over(cost, budget): return budget is not None and cost > budget
|
|
450
462
|
|
|
451
463
|
class SpeedCosts(list):
|
|
452
|
-
"Each case's cost and the budget that limits it, as `(section, code, cost, budget)` rows. A cost is in copies, except in `[widths]`, where it is the 32-bit time as a fraction of the 64-bit time. A case with no budget has `None`. A case over its budget needs study, which finds either a speed bug or something its expected cost missed. `cpu` is the CPU model that a CI log reports, or `None`. `platform` is the platform whose native budgets the costs were checked against, 'macos' or 'linux', or `None` for a browser build. `copies` holds the copy lines that a log gives, as `(items, bytes, ns per item, stored)` rows, which say whether `rustymath::timing::COPIES` held that machine's copy time or the test measured it."
|
|
464
|
+
"Each case's cost and the budget that limits it, as `(section, code, cost, budget, ns)` rows. A cost is in copies, except in `[widths]`, where it is the 32-bit time as a fraction of the 64-bit time. A case with no budget has `None`. `ns` is the case's time for each item in nanoseconds, or `None` where the timing doesn't give it. Times compare across a change to `rustymath::timing::COPIES`, where costs don't. A case over its budget needs study, which finds either a speed bug or something its expected cost missed. `cpu` is the CPU model that a CI log reports, or `None`. `platform` is the platform whose native budgets the costs were checked against, 'macos' or 'linux', or `None` for a browser build. `copies` holds the copy lines that a log gives, as `(items, bytes, ns per item, stored)` rows, which say whether `rustymath::timing::COPIES` held that machine's copy time or the test measured it."
|
|
453
465
|
def __init__(self, rows=(), cpu=None, platform=None, copies=()):
|
|
454
466
|
super().__init__(rows)
|
|
455
467
|
self.cpu, self.platform, self.copies = cpu, platform, list(copies)
|
|
456
|
-
def __repr__(self): return '\n'.join(f'{cost:>9.2f} {_or_dash(b, "g"):>9} {"over" if _over(cost, b) else "":4} {s} {c}' for s, c, cost, b in self)
|
|
468
|
+
def __repr__(self): return '\n'.join(f'{cost:>9.2f} {_or_dash(b, "g"):>9} {_or_dash(ns, ".3f"):>9} {"over" if _over(cost, b) else "":4} {s} {c}' for s, c, cost, b, ns in self)
|
|
457
469
|
|
|
458
470
|
def speed_costs(
|
|
459
471
|
wasm:Path=None, # A directory that `cargo wasm --out` wrote, to time in that browser build, as `tests/wasm-speed.mjs` does; None: a fresh native worker, as `tests/speed.rs` does
|
|
@@ -481,44 +493,47 @@ def speed_costs(
|
|
|
481
493
|
rows = json.loads(p.stdout)
|
|
482
494
|
build = _platform() if wasm is None else 'wasm'
|
|
483
495
|
i = _BUDGETS.index(build)
|
|
484
|
-
rows = ((c['section'], c['code'], c['cost'], float(c['budgets'][i]) if len(c['budgets']) > i else None) for c in rows)
|
|
496
|
+
rows = ((c['section'], c['code'], c['cost'], float(c['budgets'][i]) if len(c['budgets']) > i else None, c['seconds']*1e9 if 'seconds' in c else None) for c in rows) # A saved `bpl` from before 23 October 2026 gives no `seconds`
|
|
485
497
|
return SpeedCosts(rows, platform=None if wasm else build)
|
|
486
498
|
|
|
487
|
-
_LOGGED = re.compile(r'^(?:\S*Z )?(?P<tap># )?(?P<section>\w+) (?P<code>\S.*): (?P<cost>[\d.]+|inf|NaN|Infinity), budget (?P<budget>[\d.]+)(?:, [\d.]+ ns)?$')
|
|
488
|
-
|
|
489
|
-
_COPY = re.compile(r'^(?:\S*Z )?(?:# )?copy of (?P<items>\d+) (?P<bytes>\d+)-byte items: (?P<ns>[\d.]+) ns an item, (?P<source>stored|measured)', re.M)
|
|
499
|
+
_LOGGED = re.compile(r'^(?:\S*Z )?(?P<tap># )?(?P<section>\w+) (?P<code>\S.*): (?P<cost>[\d.]+|inf|NaN|Infinity), budget (?P<budget>[\d.]+)(?:, (?P<ns>[\d.]+) ns)?$')
|
|
500
|
+
_COPY = re.compile(r'^(?:\S*Z )?(?:# )?copy of (?P<items>\d+) (?P<bytes>\d+)-byte items: (?P<ns>[\d.]+) ns an item, (?P<source>stored for|measured on) (?P<cpu>[^\r\n]+?)(?:: add it to rustymath::timing::COPIES)?[ \t]*$', re.M)
|
|
490
501
|
|
|
491
502
|
def logged_costs(
|
|
492
503
|
log, # What a speed test prints with `<NAME>_COSTS` set, such as the log of a job in the `Test` or `Speed` workflow, or the path of a file that holds it
|
|
493
504
|
platform='linux' # The platform whose native budgets the log's costs were checked against: 'linux' for CI's runner, or 'macos' for a Mac
|
|
494
505
|
):
|
|
495
|
-
"The rows that `speed_costs` gives, read from `log`, with the CPU
|
|
506
|
+
"The rows that `speed_costs` gives, read from `log`, with the CPU that its copy lines name as their `cpu`, `platform`, and the log's copy lines as `copies`. A case that the log times more than once, as after a retry, gives its lowest cost, with that timing's time for each item where the log prints one."
|
|
496
507
|
if isinstance(log, Path): log = log.read_text()
|
|
497
|
-
|
|
508
|
+
found = list(_COPY.finditer(log))
|
|
509
|
+
cpu = next((m['cpu'] for m in found if m['cpu'] != 'a CPU with no name'), None)
|
|
498
510
|
rows = {}
|
|
499
511
|
for m in filter(None, map(_LOGGED.match, log.splitlines())):
|
|
500
512
|
code = re.sub(r'\\([\\#])', r'\1', m['code']) if m['tap'] else m['code'] # Node's TAP diagnostics escape `\` and `#`
|
|
501
|
-
row = (m['section'], code, float(m['cost']), float(m['budget']))
|
|
513
|
+
row = (m['section'], code, float(m['cost']), float(m['budget']), m['ns'] and float(m['ns']))
|
|
502
514
|
if row[:2] not in rows or row[2] < rows[row[:2]][2]: rows[row[:2]] = row
|
|
503
|
-
copies = list(dict.fromkeys((int(m['items']), int(m['bytes']), float(m['ns']), m['source'] == 'stored') for m in
|
|
515
|
+
copies = list(dict.fromkeys((int(m['items']), int(m['bytes']), float(m['ns']), m['source'] == 'stored for') for m in found))
|
|
504
516
|
return SpeedCosts(rows.values(), cpu, platform, copies)
|
|
505
517
|
|
|
506
518
|
CHANGE = 0.2
|
|
507
519
|
class CostChanges(list):
|
|
508
|
-
"The cases that `compare_costs` reports, as `(section, code, before, after, budget)` rows. Cases over their budget in `after` come first, then cases whose
|
|
509
|
-
def __repr__(self): return '\n'.join(f'{_or_dash(b, ".2f"):>9} {a:>9.2f} {_or_dash(
|
|
520
|
+
"The cases that `compare_costs` reports, as `(section, code, before, after, budget, change)` rows. `before` and `after` are costs. `change` is the fractional change in the case's time for each item where both runs give one, and in its cost otherwise, so it holds across a change to `rustymath::timing::COPIES`. Cases over their budget in `after` come first, then cases whose change is more than `CHANGE`, largest first. `before` and `change` are `None` for a case that the earlier run didn't time, and `budget` is `None` for a case with no budget."
|
|
521
|
+
def __repr__(self): return '\n'.join(f'{_or_dash(b, ".2f"):>9} {a:>9.2f} {_or_dash(d, "+.0%"):>6} {_or_dash(budget, "g"):>9} {"over" if _over(a, budget) else "":4} {s} {c}' for s, c, b, a, budget, d in self)
|
|
510
522
|
|
|
511
523
|
def compare_costs(
|
|
512
524
|
before:Annotated[SpeedCosts|str|Path, {'type':Path}], # The earlier run: rows that `speed_costs`, `logged_costs`, `cargo_costs` or `pytest_costs` give, or a log that `logged_costs` reads, as its text or its path
|
|
513
525
|
after:Annotated[SpeedCosts|str|Path, {'type':Path}], # The later run, in any of `before`'s forms
|
|
514
526
|
platform='linux' # The platform whose native budgets a log's costs were checked against, as `logged_costs` takes it
|
|
515
527
|
):
|
|
516
|
-
"Each case over its budget in `after`, and each case whose
|
|
528
|
+
"Each case over its budget in `after`, and each case whose time for each item moved by more than `CHANGE` from `before` in either direction, or whose cost did where either run doesn't give the time."
|
|
517
529
|
before, after = (x if isinstance(x, SpeedCosts) else logged_costs(x, platform) for x in (before, after))
|
|
518
|
-
old = {(s, c): cost for s, c, cost, _ in before}
|
|
519
|
-
rows = [
|
|
520
|
-
|
|
521
|
-
|
|
530
|
+
old = {(s, c): (cost, ns) for s, c, cost, _, ns in before}
|
|
531
|
+
rows = []
|
|
532
|
+
for s, c, cost, budget, ns in after:
|
|
533
|
+
cost0, ns0 = old.get((s, c), (None, None))
|
|
534
|
+
rows.append((s, c, cost0, cost, budget, ns/ns0 - 1 if ns and ns0 else cost/cost0 - 1 if cost0 else None))
|
|
535
|
+
def moved(row): return abs(row[5] or 0)
|
|
536
|
+
return CostChanges(sorted((r for r in rows if _over(r[3], r[4]) or moved(r) > CHANGE), key=lambda r: (not _over(r[3], r[4]), -moved(r))))
|
|
522
537
|
|
|
523
538
|
def _run_costs(cmd, path, timeout, native=False, env=None):
|
|
524
539
|
"The rows that `logged_costs` reads from what `cmd` prints, run in the package at `path` with `<NAME>_COSTS` set. A case over its budget still gives its row."
|
|
@@ -5,7 +5,6 @@ from pathlib import Path
|
|
|
5
5
|
from fastcore.script import call_parse
|
|
6
6
|
from fastcore.xtras import working_directory
|
|
7
7
|
from fastcore.nbio import write_nb
|
|
8
|
-
from fastws.cargo import timing_lock
|
|
9
8
|
from . import bpl
|
|
10
9
|
|
|
11
10
|
LANGUAGES = {'bpl', 'hpl'}
|
|
@@ -34,6 +33,8 @@ def main(
|
|
|
34
33
|
save:bool=False, # Save outputs in the notebook
|
|
35
34
|
):
|
|
36
35
|
"Run BPL notebooks without starting Jupyter, holding the timing lock shared."
|
|
36
|
+
try: from fastws.cargo import timing_lock
|
|
37
|
+
except ImportError: raise SystemExit("bpl-nb runs basedpl's own notebooks and needs the dev extras: pip install 'basedpl[dev]'")
|
|
37
38
|
with timing_lock():
|
|
38
39
|
paths = sorted(path.rglob('*.ipynb')) if path.is_dir() else [path]
|
|
39
40
|
for p in paths:
|
|
@@ -357,12 +357,12 @@ impl Bindings {
|
|
|
357
357
|
|
|
358
358
|
/// Pushes `frame` and makes it current. Its first slots hold `implicit`, the bindings of the six names that a call binds itself, in slot order (`Name::implicit`), and its locals' slots are empty. Fails at the depth limit, before pushing.
|
|
359
359
|
#[inline]
|
|
360
|
-
pub(super) fn enter(&self, mut frame: Frame, implicit: [Option<Binding>; IMPLICIT], span: &Span) -> Result<(), Error> {
|
|
360
|
+
pub(super) fn enter(&self, mut frame: Frame, mut implicit: [Option<Binding>; IMPLICIT], span: &Span) -> Result<(), Error> {
|
|
361
361
|
let mut stack = self.stack.borrow_mut();
|
|
362
362
|
if stack.frames.len() == MAX_FRAME_DEPTH { return Err(Error::new(Kind::Limit, format!("lexical frame depth exceeds {MAX_FRAME_DEPTH}")).at(span)); }
|
|
363
363
|
frame.base = stack.slots.len();
|
|
364
|
-
stack.slots.extend(implicit);
|
|
365
364
|
stack.slots.resize_with(frame.slots().end, || None);
|
|
365
|
+
stack.slots[frame.base..frame.base + IMPLICIT].swap_with_slice(&mut implicit);
|
|
366
366
|
self.current.set(Some(stack.frames.len()));
|
|
367
367
|
stack.frames.push(frame);
|
|
368
368
|
Ok(())
|
|
@@ -617,11 +617,10 @@ impl Session {
|
|
|
617
617
|
|
|
618
618
|
// Inlined into `dispatch`, this would add its locals to the stack frame of every BPL call, limiting recursion in the browser.
|
|
619
619
|
#[inline(never)]
|
|
620
|
-
pub(super) fn call_defined(&self,
|
|
621
|
-
let return_span = function.definition().expect("call_defined calls a dfn").span.clone();
|
|
620
|
+
pub(super) fn call_defined(&self, first: &Function, left: Option<&Value>, right: &Value) -> Result<Bound, Error> {
|
|
622
621
|
let caller = self.bindings.current();
|
|
623
622
|
let base = self.bindings.depth();
|
|
624
|
-
let (mut function, mut left, mut right) = (
|
|
623
|
+
let (mut function, mut left, mut right) = (first.clone(), left.cloned(), right.clone());
|
|
625
624
|
let mut tail_span = None;
|
|
626
625
|
let mut unshy = false;
|
|
627
626
|
let mut result = loop {
|
|
@@ -653,7 +652,7 @@ impl Session {
|
|
|
653
652
|
};
|
|
654
653
|
if let (Err(error), Some(span)) = (&mut result, tail_span) { error.calls.push(span); }
|
|
655
654
|
if let Ok(bound) = &mut result { if unshy { bound.shy = false; } }
|
|
656
|
-
self.leave(base, caller, result, &
|
|
655
|
+
self.leave(base, caller, result, &first.definition().expect("call_defined calls a dfn").span)
|
|
657
656
|
}
|
|
658
657
|
|
|
659
658
|
/// Drops the frames from `base` on, makes `caller` current and gives back `result`. A result that refers to a dropped frame is an error.
|
|
@@ -883,7 +883,7 @@ impl Number {
|
|
|
883
883
|
|
|
884
884
|
fn minimum(&self, right: &Self, maximum: bool) -> Result<Self, &'static str> {
|
|
885
885
|
if let (Integer(x), Integer(y)) = (&self.0, &right.0) { return Ok(Self(Integer(if maximum { *x.max(y) } else { *x.min(y) }))); }
|
|
886
|
-
// NaN
|
|
886
|
+
// Beside NaN it gives `right` at the width the two compute at, as `rustymath::reduce::maximum` gives its `y`. An exact number stays exact beside an infinity, and beside another exact number.
|
|
887
887
|
let nan = self.is_nan() || right.is_nan();
|
|
888
888
|
if !nan && ((self.is_exact() && right.is_exact()) || self.is_infinite() || right.is_infinite()) {
|
|
889
889
|
return Ok((if maximum == self.order(right)?.is_lt() { right } else { self }).clone());
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! The speed test's measurements, shared by `tests/speed.rs`, the browser build's `costs` and the worker's `timing` and `costs` requests, which serve `basedpl.bpltests.time_cases` and `speed_costs`. A time is the fastest of five evaluations of a case, after one to warm up. The case is parsed once, before them. Its unit is one copy of the `n` items in the data of the `[setup]` before the case's section, as `rustymath::timing::copy_time` gives it: this machine's stored copy time, or one measured once. `costs` and `request` hold the machine's timing lock exclusively while they run, as `rustymath::timing` describes.
|
|
2
2
|
use crate::{host::timing_lock, protocol::response, EvalOptions, Evaluation, InterruptHandle, Session, Source, Turn};
|
|
3
|
-
use rustymath::timing::{code_of, copy_time, header, sections, CopyTime};
|
|
3
|
+
use rustymath::timing::{code_of, copy_time, header, sections, CopyTime, NATIVE_COLUMN};
|
|
4
|
+
pub use rustymath::timing::name_cpu;
|
|
4
5
|
use serde_json::{json, Value as JsonValue};
|
|
5
6
|
use rustymath::hashing::{HashMap, HashMapExt};
|
|
6
7
|
|
|
@@ -69,15 +70,23 @@ pub fn time(session: &Session, code: &str, interrupt: &InterruptHandle) -> Resul
|
|
|
69
70
|
failed.map_or(Ok(seconds), Err)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/// The column of a case's wasm budget in basedpl's `tests/speed.txt`, after the macOS and Linux budgets that `rustymath::timing::NATIVE_COLUMN` picks from.
|
|
74
|
+
pub const WASM_COLUMN: usize = 2;
|
|
75
|
+
|
|
72
76
|
/// One case of a file in `speed.txt`'s format, with its cost: the section it's in, its code, its budgets in the file's order, the cost those budgets limit, its time for each of the section's `n` items in seconds, and the copy that its cost counts, which `[widths]` costs don't use.
|
|
73
77
|
pub struct Case<'a> { pub section: &'a str, pub code: &'a str, pub budgets: Vec<&'a str>, pub cost: f64, pub seconds: f64, pub copy: Option<CopyTime> }
|
|
74
78
|
impl Case<'_> {
|
|
75
|
-
/// This case's line in a speed test's log, with the budget in `column` of its budgets:
|
|
79
|
+
/// This case's line in a speed test's log, with the budget in `column` of its budgets: `rustymath::timing::NATIVE_COLUMN` for this platform's native budget, or `WASM_COLUMN`. `basedpl.bpltests.logged_costs` reads these lines.
|
|
76
80
|
pub fn line(&self, column: usize) -> String { rustymath::timing::line(self.section, self.code, self.cost, self.budgets.get(column).copied().unwrap_or("-"), self.seconds) }
|
|
77
81
|
/// The copy line of the copy that this case's cost counts, as `rustymath::timing::copy_line` writes it. `None` in `[widths]`.
|
|
78
82
|
pub fn copy_line(&self) -> Option<String> { self.copy.as_ref().map(rustymath::timing::copy_line) }
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/// Each of `cases` as JSON, as the worker's `costs` reply and the browser build's `costs` give it: its section, code, log `line` with the budget in `column` of its budgets, `copy` line or null, budgets, cost, and time for each item in `seconds`.
|
|
86
|
+
pub fn rows(cases: &[Case], column: usize) -> JsonValue {
|
|
87
|
+
cases.iter().map(|c| json!({"section": c.section, "code": c.code, "line": c.line(column), "copy": c.copy_line(), "budgets": c.budgets, "cost": c.cost, "seconds": c.seconds})).collect()
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
/// One group of sections, which share the group's `[setup]` and one session, set up for one section at a time: each section's `•prefs` header, the section the session is set up for, and the setup's `n`.
|
|
82
91
|
struct Group<'a> { session: Turn, setup: String, prefs: HashMap<&'a str, &'a str>, current: &'a str, n: usize }
|
|
83
92
|
|
|
@@ -218,13 +227,11 @@ pub fn request(spec: &JsonValue, interrupt: &InterruptHandle) -> Result<JsonValu
|
|
|
218
227
|
Ok(json!({"n": n, "costs": costs, "output": [], "error": null}))
|
|
219
228
|
}
|
|
220
229
|
|
|
221
|
-
/// The reply to the worker's `costs` request, which gives `speed`, text in `speed.txt`'s format, and optionally `cases`, a list of the codes to time. The reply gives each case with its cost, as `costs` times them
|
|
230
|
+
/// The reply to the worker's `costs` request, which gives `speed`, text in `speed.txt`'s format, and optionally `cases`, a list of the codes to time. The reply gives each case with its cost, as `costs` times them and `rows` encodes them, with this platform's native budget in each log line.
|
|
222
231
|
pub fn file_request(spec: &JsonValue, interrupt: &InterruptHandle) -> Result<JsonValue, String> {
|
|
223
232
|
let only: Option<Vec<&str>> = match spec.get("cases") {
|
|
224
233
|
None | Some(JsonValue::Null) => None,
|
|
225
234
|
Some(cases) => Some(cases.as_array().and_then(|c| c.iter().map(JsonValue::as_str).collect()).ok_or("costs needs cases as a list of strings")?),
|
|
226
235
|
};
|
|
227
|
-
|
|
228
|
-
let cases: Vec<_> = cases.iter().map(|c| json!({"section": c.section, "code": c.code, "budgets": c.budgets, "cost": c.cost})).collect();
|
|
229
|
-
Ok(json!({"costs": cases, "error": null}))
|
|
236
|
+
Ok(json!({"costs": rows(&costs(spec["speed"].as_str().ok_or("costs needs speed as a string")?, only.as_deref(), interrupt)?, NATIVE_COLUMN), "error": null}))
|
|
230
237
|
}
|
|
@@ -4759,10 +4759,14 @@ $t $t $t $t $t $t $t
|
|
|
4759
4759
|
$t $t $t $t
|
|
4760
4760
|
$t $t $t $t $t
|
|
4761
4761
|
|
|
4762
|
-
⍝ — Under dynamic kinds, ⌊ and ⌈ keep an exact number exact beside an infinity
|
|
4762
|
+
⍝ — Under dynamic kinds, ⌊ and ⌈ keep an exact number exact beside an infinity, and give their right argument beside NaN, as a float. On a large array they don't fuse beside an exact number or a comparison, because a fused graph reads them as floats
|
|
4763
4763
|
x←¿1000000⍴0 ⋄ x.[[0 1 2 3]]←[∞ ¯∞ $n 0] ⋄ [4↑x⌊0ₓ ⋄ 4↑x⌈0ₓ ⋄ 4↑x⌊x>0 ⋄ 4↑x⌈$t]
|
|
4764
4764
|
[0ₓ ¯∞ 0 0⋄∞ 0ₓ 0 0⋄1ₓ ¯∞ 0 0⋄∞ 1ₓ 1 1]
|
|
4765
4765
|
|
|
4766
|
+
⍝ max-min-nan — ⌈ and ⌊ give their right argument beside NaN, and every fold, scan and product starts at ¯∞ or ∞ with each item on the left, so a NaN item changes no running result and a NaN seed stays: dyadic in either order, folds and scans short and long, columns, Key, seeded folds, windows, rows, products, exact and mixed numbers, and a long fold split into pieces
|
|
4767
|
+
nans←{+/,~⍵=⍵} ⋄ r←⌈\(⍳10),$n,⍳53 ⋄ m←64 64⍴0.5+⍳4096 ⋄ m.[3 5]←$n ⋄ q←1000 4⍴0.5+⍳4000 ⋄ q.[7 2]←$n ⋄ p←100 20⍴0.5+⍳2000 ⋄ p.[3 4]←$n ⋄ w←0.5+⍳200 ⋄ w.[100]←$n ⋄ s←0.5+⍳1000000 ⋄ s.[900000]←$n ⋄ s.[900001]←1e9 ⋄ [$n⌈3;3⌈$n;$n⌊3;3⌊$n;⌈/1 $n 3;⌊/1 $n 3;⌈/$n,⍳15;⌈/(⍳15),$n;⌈/$n $n;⌈\$n 1 2;⌊\1 $n 3;nans ⌈\$n,⍳63;10↑r;nans r;⌈⌿[1 $n⋄3 4];nans ⌈⌿m;0 1 0 1 (⌈/⊢)⌸ 1 $n 3 4;0⌈/$n 1 2;0⌈/$n,⍳20;⌈/3↕1 $n 3 4 5;nans ⌈/50↕w;⌈/⍤1 [1 $n 3⋄4 5 6];nans ⌈/⍤1 q;nans ⌈/⍤1 p;nans ⌈/⍤1 m;[1 $n⋄3 4]⌈.+2 2⍴0;nans m⌈.+m;nans m⌊.+m;1ₓ⌈$n;$n⌊1ₓ;⌈/1ₓ $n 2.5;⌈/s;⌊/s;$n⌈/⍳40;nans $n⌈\⍳300]
|
|
4768
|
+
[3 $n 3 $n 3 1 14 14 ¯∞ [¯∞ 1 2] [1 1 1] 0ₓ [0 1 2 3 4 5 6 7 8 9] 0ₓ [3 4] 0ₓ [3 4] 2 19 [3 4 5] 0ₓ [3 6] 0ₓ 0ₓ 0ₓ [1 1⋄4 4] 0ₓ 0ₓ $n 1 2.5 1000000000 0.5 $n 300ₓ]
|
|
4769
|
+
|
|
4766
4770
|
⍝ — With •prefs ["fusion":$t], a call that computes a pending argument reports its glyph and position
|
|
4767
4771
|
x←1.5+¿1000000⍴0 ⋄ •prefs ["fusion":$t] ⋄ r←⍴⍋x ⋄ •prefs ["fusion":$f] ⋄ r
|
|
4768
4772
|
[1000000]ₓ
|