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.
Files changed (476) hide show
  1. {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/ci.yml +1 -3
  2. {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/speed.yml +0 -2
  3. {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/test.yml +0 -2
  4. {basedpl-0.1.69 → basedpl-0.1.71}/Cargo.lock +9 -9
  5. {basedpl-0.1.69 → basedpl-0.1.71}/Cargo.toml +3 -3
  6. {basedpl-0.1.69 → basedpl-0.1.71}/DEV.md +7 -7
  7. {basedpl-0.1.69 → basedpl-0.1.71}/PKG-INFO +1 -1
  8. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/downstile.qmd +2 -1
  9. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/upstile.qmd +2 -1
  10. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/llms.txt +1 -1
  11. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/bpltests.py +43 -28
  12. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/notebook_runner.py +2 -1
  13. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/bindings.rs +2 -2
  14. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/session.rs +3 -4
  15. {basedpl-0.1.69 → basedpl-0.1.71}/src/number.rs +1 -1
  16. {basedpl-0.1.69 → basedpl-0.1.71}/src/timing.rs +13 -6
  17. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/core.bpl +5 -1
  18. {basedpl-0.1.69 → basedpl-0.1.71}/tests/speed.txt +203 -265
  19. {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm-pkg.mjs +3 -0
  20. {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm-speed.mjs +7 -3
  21. {basedpl-0.1.69 → basedpl-0.1.71}/.cargo/config.toml +0 -0
  22. {basedpl-0.1.69 → basedpl-0.1.71}/.gitattributes +0 -0
  23. {basedpl-0.1.69 → basedpl-0.1.71}/.github/workflows/docs.yml +0 -0
  24. {basedpl-0.1.69 → basedpl-0.1.71}/.gitignore +0 -0
  25. {basedpl-0.1.69 → basedpl-0.1.71}/CONTRIBUTING.md +0 -0
  26. {basedpl-0.1.69 → basedpl-0.1.71}/LICENSE +0 -0
  27. {basedpl-0.1.69 → basedpl-0.1.71}/README.md +0 -0
  28. {basedpl-0.1.69 → basedpl-0.1.71}/_config.yml +0 -0
  29. {basedpl-0.1.69 → basedpl-0.1.71}/_layouts/default.html +0 -0
  30. {basedpl-0.1.69 → basedpl-0.1.71}/build.rs +0 -0
  31. {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/Syntaxes/bpl.tmLanguage.json +0 -0
  32. {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/Syntaxes/hpl.tmLanguage.json +0 -0
  33. {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.tmbundle/info.plist +0 -0
  34. {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/BPL.xml +0 -0
  35. {basedpl-0.1.69 → basedpl-0.1.71}/editors/jetbrains/README.md +0 -0
  36. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Info.plist +0 -0
  37. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/BasedPL-de.icns +0 -0
  38. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/BasedPL-de.keylayout +0 -0
  39. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  40. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.bundle/Contents/version.plist +0 -0
  41. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-de.txt +0 -0
  42. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Info.plist +0 -0
  43. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/BasedPL-es.icns +0 -0
  44. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/BasedPL-es.keylayout +0 -0
  45. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  46. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.bundle/Contents/version.plist +0 -0
  47. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-es.txt +0 -0
  48. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Info.plist +0 -0
  49. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/BasedPL-fr.icns +0 -0
  50. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/BasedPL-fr.keylayout +0 -0
  51. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  52. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.bundle/Contents/version.plist +0 -0
  53. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-fr.txt +0 -0
  54. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Info.plist +0 -0
  55. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/BasedPL-uk.icns +0 -0
  56. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/BasedPL-uk.keylayout +0 -0
  57. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  58. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.bundle/Contents/version.plist +0 -0
  59. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-uk.txt +0 -0
  60. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Info.plist +0 -0
  61. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/BasedPL-us.icns +0 -0
  62. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/BasedPL-us.keylayout +0 -0
  63. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  64. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.bundle/Contents/version.plist +0 -0
  65. {basedpl-0.1.69 → basedpl-0.1.71}/editors/macos/BasedPL-us.txt +0 -0
  66. {basedpl-0.1.69 → basedpl-0.1.71}/editors/vim/ftdetect/bpl.vim +0 -0
  67. {basedpl-0.1.69 → basedpl-0.1.71}/editors/vim/syntax/bpl.vim +0 -0
  68. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/README.md +0 -0
  69. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/build.ps1 +0 -0
  70. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/install.ps1 +0 -0
  71. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.c +0 -0
  72. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.def +0 -0
  73. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/keyboard.rc +0 -0
  74. {basedpl-0.1.69 → basedpl-0.1.71}/editors/windows/layout.h +0 -0
  75. {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-commonmark/cell-17-output-1.svg +0 -0
  76. {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-commonmark/cell-18-output-1.svg +0 -0
  77. {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-html/cell-17-output-1.svg +0 -0
  78. {basedpl-0.1.69 → basedpl-0.1.71}/index_files/figure-html/cell-18-output-1.svg +0 -0
  79. {basedpl-0.1.69 → basedpl-0.1.71}/lib/LICENSE-april +0 -0
  80. {basedpl-0.1.69 → basedpl-0.1.71}/lib/README.md +0 -0
  81. {basedpl-0.1.69 → basedpl-0.1.71}/lib/array.bpl +0 -0
  82. {basedpl-0.1.69 → basedpl-0.1.71}/lib/dyalog.bpl +0 -0
  83. {basedpl-0.1.69 → basedpl-0.1.71}/lib/graph.bpl +0 -0
  84. {basedpl-0.1.69 → basedpl-0.1.71}/lib/numeric.bpl +0 -0
  85. {basedpl-0.1.69 → basedpl-0.1.71}/lib/power.bpl +0 -0
  86. {basedpl-0.1.69 → basedpl-0.1.71}/lib/regex.bpl +0 -0
  87. {basedpl-0.1.69 → basedpl-0.1.71}/lib/string.bpl +0 -0
  88. {basedpl-0.1.69 → basedpl-0.1.71}/lib/tree.bpl +0 -0
  89. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/.gitignore +0 -0
  90. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/_quarto.yml +0 -0
  91. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/arrays.ipynb +0 -0
  92. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/assignment.ipynb +0 -0
  93. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bpl.xml +0 -0
  94. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bqn.xml +0 -0
  95. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/bracket-types.ipynb +0 -0
  96. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/cli.qmd +0 -0
  97. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/comparison.qmd +0 -0
  98. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/data.ipynb +0 -0
  99. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dfns.ipynb +0 -0
  100. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/distributions.ipynb +0 -0
  101. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/driver.js +0 -0
  102. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dyalog.ipynb +0 -0
  103. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/dyalog.xml +0 -0
  104. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/evaluation.qmd +0 -0
  105. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/LICENSE-SAX2 +0 -0
  106. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/README.md +0 -0
  107. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/SAX2B.ttf +0 -0
  108. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts/SAX2B.woff2 +0 -0
  109. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/fonts.css +0 -0
  110. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/attractor.bpl +0 -0
  111. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/boids.bpl +0 -0
  112. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/dragon.bpl +0 -0
  113. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/flow.bpl +0 -0
  114. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/mandelbrot.bpl +0 -0
  115. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/mandelbrot32.bpl +0 -0
  116. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/orbits.bpl +0 -0
  117. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/phyllotaxis.bpl +0 -0
  118. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/prefs.bpl +0 -0
  119. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery/reaction.bpl +0 -0
  120. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/gallery.qmd +0 -0
  121. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/getting-started.ipynb +0 -0
  122. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/_metadata.yml +0 -0
  123. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/alpha-underbar.qmd +0 -0
  124. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/alpha.qmd +0 -0
  125. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/at.qmd +0 -0
  126. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/backslash-bar.qmd +0 -0
  127. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/backslash.qmd +0 -0
  128. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/braces.qmd +0 -0
  129. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/brackets.qmd +0 -0
  130. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/bullet.qmd +0 -0
  131. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-backslash.qmd +0 -0
  132. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-bar.qmd +0 -0
  133. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-diaeresis.qmd +0 -0
  134. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-divide.qmd +0 -0
  135. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-slash.qmd +0 -0
  136. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-star.qmd +0 -0
  137. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-stile.qmd +0 -0
  138. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle-times.qmd +0 -0
  139. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/circle.qmd +0 -0
  140. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/colon.qmd +0 -0
  141. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/comma-bar.qmd +0 -0
  142. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/comma.qmd +0 -0
  143. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del-diaeresis.qmd +0 -0
  144. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del-stile.qmd +0 -0
  145. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/del.qmd +0 -0
  146. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/delta-stile.qmd +0 -0
  147. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diaeresis.qmd +0 -0
  148. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diamond-underbar.qmd +0 -0
  149. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/diamond.qmd +0 -0
  150. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/domino.qmd +0 -0
  151. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/dot.qmd +0 -0
  152. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-arrow.qmd +0 -0
  153. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-shoe.qmd +0 -0
  154. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/down-tack.qmd +0 -0
  155. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/epsilon-underbar.qmd +0 -0
  156. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/epsilon.qmd +0 -0
  157. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equal-underbar-slash.qmd +0 -0
  158. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equal-underbar.qmd +0 -0
  159. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/equals.qmd +0 -0
  160. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/error-guard.qmd +0 -0
  161. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/exclamation.qmd +0 -0
  162. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/greater-than-or-equal.qmd +0 -0
  163. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/greater-than.qmd +0 -0
  164. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/hash.qmd +0 -0
  165. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/hydrant.qmd +0 -0
  166. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/infinity.qmd +0 -0
  167. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/inverted-question.qmd +0 -0
  168. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/iota-underbar.qmd +0 -0
  169. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/iota.qmd +0 -0
  170. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/jot-diaeresis.qmd +0 -0
  171. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/jot.qmd +0 -0
  172. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/lamp.qmd +0 -0
  173. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-arrow.qmd +0 -0
  174. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-shoe-underbar.qmd +0 -0
  175. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-shoe.qmd +0 -0
  176. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-tack.qmd +0 -0
  177. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/left-tail-arrow.qmd +0 -0
  178. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/less-than-or-equal.qmd +0 -0
  179. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/less-than.qmd +0 -0
  180. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/minus.qmd +0 -0
  181. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/not-equals.qmd +0 -0
  182. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/obelus.qmd +0 -0
  183. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/omega-underbar.qmd +0 -0
  184. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/omega.qmd +0 -0
  185. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/overbar.qmd +0 -0
  186. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/parentheses.qmd +0 -0
  187. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/partial.qmd +0 -0
  188. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/pi.qmd +0 -0
  189. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/plus.qmd +0 -0
  190. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-colon.qmd +0 -0
  191. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-diamond.qmd +0 -0
  192. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-equal.qmd +0 -0
  193. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad-jot.qmd +0 -0
  194. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quad.qmd +0 -0
  195. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/question.qmd +0 -0
  196. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/quote.qmd +0 -0
  197. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/radical.qmd +0 -0
  198. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/rho.qmd +0 -0
  199. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-arrow-from-bar.qmd +0 -0
  200. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-left-arrows.qmd +0 -0
  201. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-shoe.qmd +0 -0
  202. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-tack.qmd +0 -0
  203. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/right-tail-arrow.qmd +0 -0
  204. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/slash-bar.qmd +0 -0
  205. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/slash.qmd +0 -0
  206. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/squad.qmd +0 -0
  207. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/star-diaeresis.qmd +0 -0
  208. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/star.qmd +0 -0
  209. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/stile-tilde.qmd +0 -0
  210. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/stile.qmd +0 -0
  211. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/superscript-b.qmd +0 -0
  212. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/thorn.qmd +0 -0
  213. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/tilde-diaeresis.qmd +0 -0
  214. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/tilde.qmd +0 -0
  215. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/times.qmd +0 -0
  216. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-arrow.qmd +0 -0
  217. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-down-arrow.qmd +0 -0
  218. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-shoe.qmd +0 -0
  219. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/up-tack.qmd +0 -0
  220. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/vee-bar.qmd +0 -0
  221. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/vee.qmd +0 -0
  222. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/wedge-bar.qmd +0 -0
  223. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/wedge.qmd +0 -0
  224. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs/zilde.qmd +0 -0
  225. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs-study.qmd +0 -0
  226. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/glyphs.qmd +0 -0
  227. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hanzi-notation-zh.ipynb +0 -0
  228. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hanzi-notation.ipynb +0 -0
  229. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/hpl.xml +0 -0
  230. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/index.ipynb +0 -0
  231. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/indexing.ipynb +0 -0
  232. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/introspection.ipynb +0 -0
  233. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/keyboard.qmd +0 -0
  234. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/keyed.ipynb +0 -0
  235. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/magics.ipynb +0 -0
  236. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/modules.qmd +0 -0
  237. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/nbdev.yml +0 -0
  238. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/notebooks.ipynb +0 -0
  239. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/numbers.qmd +0 -0
  240. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/overview.ipynb +0 -0
  241. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/aplwiki-arrays.md +0 -0
  242. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/brickman-depth-1-scalars.md +0 -0
  243. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/papers/mercer-based-system.md +0 -0
  244. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/bpl.js +0 -0
  245. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/gallery.js +0 -0
  246. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/hpl.tmLanguage.json +0 -0
  247. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/monaco-worker.js +0 -0
  248. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/page.js +0 -0
  249. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground/worker.js +0 -0
  250. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/playground.qmd +0 -0
  251. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/plot.ipynb +0 -0
  252. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/principles.ipynb +0 -0
  253. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/processes.qmd +0 -0
  254. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/puppy.jpg +0 -0
  255. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/python.ipynb +0 -0
  256. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/regex.ipynb +0 -0
  257. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/repl.qmd +0 -0
  258. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/scripts.ipynb +0 -0
  259. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/sidebar.yml +0 -0
  260. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/strings.ipynb +0 -0
  261. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/01_basics.ipynb +0 -0
  262. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/02_functions.ipynb +0 -0
  263. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/03_operators.ipynb +0 -0
  264. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/04_nested-arrays.ipynb +0 -0
  265. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/05_searching-indexing.ipynb +0 -0
  266. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/06_rearranging-arrays.ipynb +0 -0
  267. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/study/07_numerical-tools.ipynb +0 -0
  268. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/style.qmd +0 -0
  269. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/styles.css +0 -0
  270. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/syntax.qmd +0 -0
  271. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/_metadata.yml +0 -0
  272. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/c.qmd +0 -0
  273. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/canvas.qmd +0 -0
  274. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/constants.qmd +0 -0
  275. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/copy.qmd +0 -0
  276. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/csv.qmd +0 -0
  277. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/date.qmd +0 -0
  278. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/decompose.qmd +0 -0
  279. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/deflate.qmd +0 -0
  280. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/delay.qmd +0 -0
  281. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/detach.qmd +0 -0
  282. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/distribution.qmd +0 -0
  283. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/edge.qmd +0 -0
  284. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/element.qmd +0 -0
  285. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/ex.qmd +0 -0
  286. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/explain.qmd +0 -0
  287. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/fetch.qmd +0 -0
  288. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/float.qmd +0 -0
  289. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/hash.qmd +0 -0
  290. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/host.qmd +0 -0
  291. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/image.qmd +0 -0
  292. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/index.qmd +0 -0
  293. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/js.qmd +0 -0
  294. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/json.qmd +0 -0
  295. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/literal.qmd +0 -0
  296. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/load.qmd +0 -0
  297. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/memory.qmd +0 -0
  298. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/metadata.qmd +0 -0
  299. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/mime.qmd +0 -0
  300. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/mkdir.qmd +0 -0
  301. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nc.qmd +0 -0
  302. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nget.qmd +0 -0
  303. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nl.qmd +0 -0
  304. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/normalize.qmd +0 -0
  305. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/nput.qmd +0 -0
  306. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/path.qmd +0 -0
  307. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/plot.qmd +0 -0
  308. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/prefs.qmd +0 -0
  309. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/r.qmd +0 -0
  310. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/rand.qmd +0 -0
  311. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/readdir.qmd +0 -0
  312. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/remove.qmd +0 -0
  313. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/rename.qmd +0 -0
  314. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/signal.qmd +0 -0
  315. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/src.qmd +0 -0
  316. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/storage.qmd +0 -0
  317. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/svg.qmd +0 -0
  318. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/time.qmd +0 -0
  319. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/ucs.qmd +0 -0
  320. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/uuid.qmd +0 -0
  321. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/vfi.qmd +0 -0
  322. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system/xml.qmd +0 -0
  323. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/system-functions.qmd +0 -0
  324. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/terms.qmd +0 -0
  325. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/web.ipynb +0 -0
  326. {basedpl-0.1.69 → basedpl-0.1.71}/nbs/xml.ipynb +0 -0
  327. {basedpl-0.1.69 → basedpl-0.1.71}/package-lock.json +0 -0
  328. {basedpl-0.1.69 → basedpl-0.1.71}/package.json +0 -0
  329. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/README.md +0 -0
  330. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/aplcart.jsonl +0 -0
  331. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/april.jsonl +0 -0
  332. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/bqn.jsonl +0 -0
  333. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/core.jsonl +0 -0
  334. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/dyalog.jsonl +0 -0
  335. {basedpl-0.1.69 → basedpl-0.1.71}/provenance/ngn.jsonl +0 -0
  336. {basedpl-0.1.69 → basedpl-0.1.71}/py/Cargo.toml +0 -0
  337. {basedpl-0.1.69 → basedpl-0.1.71}/py/build.rs +0 -0
  338. {basedpl-0.1.69 → basedpl-0.1.71}/py/src/lib.rs +0 -0
  339. {basedpl-0.1.69 → basedpl-0.1.71}/pyproject.toml +0 -0
  340. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/__init__.py +0 -0
  341. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/_modidx.py +0 -0
  342. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/bpl.tmLanguage.json +0 -0
  343. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/dyalog.py +0 -0
  344. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/editors.py +0 -0
  345. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/font.py +0 -0
  346. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/fonts.css +0 -0
  347. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/functions.py +0 -0
  348. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/hpl.tmLanguage.json +0 -0
  349. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/input.js +0 -0
  350. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/ipython.py +0 -0
  351. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/keyboards.json +0 -0
  352. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/keyboards.py +0 -0
  353. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/layout.json +0 -0
  354. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/lb.js +0 -0
  355. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/notebooks.py +0 -0
  356. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/printing.py +0 -0
  357. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/reference.py +0 -0
  358. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/syntax.py +0 -0
  359. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/windows_keyboard.py +0 -0
  360. {basedpl-0.1.69 → basedpl-0.1.71}/python/basedpl/worker.py +0 -0
  361. {basedpl-0.1.69 → basedpl-0.1.71}/rustfmt.toml +0 -0
  362. {basedpl-0.1.69 → basedpl-0.1.71}/scripts/install-macos.sh +0 -0
  363. {basedpl-0.1.69 → basedpl-0.1.71}/scripts/prep.py +0 -0
  364. {basedpl-0.1.69 → basedpl-0.1.71}/src/agreement.rs +0 -0
  365. {basedpl-0.1.69 → basedpl-0.1.71}/src/array/storage.rs +0 -0
  366. {basedpl-0.1.69 → basedpl-0.1.71}/src/array/text.rs +0 -0
  367. {basedpl-0.1.69 → basedpl-0.1.71}/src/array.rs +0 -0
  368. {basedpl-0.1.69 → basedpl-0.1.71}/src/batch.rs +0 -0
  369. {basedpl-0.1.69 → basedpl-0.1.71}/src/canvas.js +0 -0
  370. {basedpl-0.1.69 → basedpl-0.1.71}/src/canvas.rs +0 -0
  371. {basedpl-0.1.69 → basedpl-0.1.71}/src/csv.rs +0 -0
  372. {basedpl-0.1.69 → basedpl-0.1.71}/src/data.rs +0 -0
  373. {basedpl-0.1.69 → basedpl-0.1.71}/src/date.rs +0 -0
  374. {basedpl-0.1.69 → basedpl-0.1.71}/src/derivative.rs +0 -0
  375. {basedpl-0.1.69 → basedpl-0.1.71}/src/display.rs +0 -0
  376. {basedpl-0.1.69 → basedpl-0.1.71}/src/distribution.rs +0 -0
  377. {basedpl-0.1.69 → basedpl-0.1.71}/src/element.rs +0 -0
  378. {basedpl-0.1.69 → basedpl-0.1.71}/src/error.rs +0 -0
  379. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/assign.rs +0 -0
  380. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/binder.rs +0 -0
  381. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/dissect.rs +0 -0
  382. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/idioms.rs +0 -0
  383. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/operators.rs +0 -0
  384. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval/tree.rs +0 -0
  385. {basedpl-0.1.69 → basedpl-0.1.71}/src/eval.rs +0 -0
  386. {basedpl-0.1.69 → basedpl-0.1.71}/src/execution.rs +0 -0
  387. {basedpl-0.1.69 → basedpl-0.1.71}/src/explain.rs +0 -0
  388. {basedpl-0.1.69 → basedpl-0.1.71}/src/files.rs +0 -0
  389. {basedpl-0.1.69 → basedpl-0.1.71}/src/fusion/blocks.rs +0 -0
  390. {basedpl-0.1.69 → basedpl-0.1.71}/src/fusion.rs +0 -0
  391. {basedpl-0.1.69 → basedpl-0.1.71}/src/host.rs +0 -0
  392. {basedpl-0.1.69 → basedpl-0.1.71}/src/host_web.rs +0 -0
  393. {basedpl-0.1.69 → basedpl-0.1.71}/src/image/codecs.rs +0 -0
  394. {basedpl-0.1.69 → basedpl-0.1.71}/src/image/imageio.rs +0 -0
  395. {basedpl-0.1.69 → basedpl-0.1.71}/src/image.rs +0 -0
  396. {basedpl-0.1.69 → basedpl-0.1.71}/src/inspection.rs +0 -0
  397. {basedpl-0.1.69 → basedpl-0.1.71}/src/js.rs +0 -0
  398. {basedpl-0.1.69 → basedpl-0.1.71}/src/json.rs +0 -0
  399. {basedpl-0.1.69 → basedpl-0.1.71}/src/keyed.rs +0 -0
  400. {basedpl-0.1.69 → basedpl-0.1.71}/src/lib.rs +0 -0
  401. {basedpl-0.1.69 → basedpl-0.1.71}/src/linear.rs +0 -0
  402. {basedpl-0.1.69 → basedpl-0.1.71}/src/name.rs +0 -0
  403. {basedpl-0.1.69 → basedpl-0.1.71}/src/number_theory.rs +0 -0
  404. {basedpl-0.1.69 → basedpl-0.1.71}/src/optimize.rs +0 -0
  405. {basedpl-0.1.69 → basedpl-0.1.71}/src/pervasive.rs +0 -0
  406. {basedpl-0.1.69 → basedpl-0.1.71}/src/plot.rs +0 -0
  407. {basedpl-0.1.69 → basedpl-0.1.71}/src/polynomial.rs +0 -0
  408. {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/indexing.rs +0 -0
  409. {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/numeric.rs +0 -0
  410. {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/search.rs +0 -0
  411. {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive/structural.rs +0 -0
  412. {basedpl-0.1.69 → basedpl-0.1.71}/src/primitive.rs +0 -0
  413. {basedpl-0.1.69 → basedpl-0.1.71}/src/protocol.rs +0 -0
  414. {basedpl-0.1.69 → basedpl-0.1.71}/src/reference.rs +0 -0
  415. {basedpl-0.1.69 → basedpl-0.1.71}/src/regex.rs +0 -0
  416. {basedpl-0.1.69 → basedpl-0.1.71}/src/search.rs +0 -0
  417. {basedpl-0.1.69 → basedpl-0.1.71}/src/selection.rs +0 -0
  418. {basedpl-0.1.69 → basedpl-0.1.71}/src/settings.rs +0 -0
  419. {basedpl-0.1.69 → basedpl-0.1.71}/src/symbols.rs +0 -0
  420. {basedpl-0.1.69 → basedpl-0.1.71}/src/syntax.rs +0 -0
  421. {basedpl-0.1.69 → basedpl-0.1.71}/src/system.rs +0 -0
  422. {basedpl-0.1.69 → basedpl-0.1.71}/src/transform.rs +0 -0
  423. {basedpl-0.1.69 → basedpl-0.1.71}/src/xml.rs +0 -0
  424. {basedpl-0.1.69 → basedpl-0.1.71}/tests/common/mod.rs +0 -0
  425. {basedpl-0.1.69 → basedpl-0.1.71}/tests/conftest.py +0 -0
  426. {basedpl-0.1.69 → basedpl-0.1.71}/tests/core.rs +0 -0
  427. {basedpl-0.1.69 → basedpl-0.1.71}/tests/derivatives.rs +0 -0
  428. {basedpl-0.1.69 → basedpl-0.1.71}/tests/explain.rs +0 -0
  429. {basedpl-0.1.69 → basedpl-0.1.71}/tests/highlighting.mjs +0 -0
  430. {basedpl-0.1.69 → basedpl-0.1.71}/tests/input.mjs +0 -0
  431. {basedpl-0.1.69 → basedpl-0.1.71}/tests/instructions.txt +0 -0
  432. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-aplcart +0 -0
  433. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-april +0 -0
  434. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-bqn +0 -0
  435. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-bqncrate +0 -0
  436. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-dyalog +0 -0
  437. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/LICENSE-ngn +0 -0
  438. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/README.md +0 -0
  439. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/aplcart.bpl +0 -0
  440. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/april.bpl +0 -0
  441. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/bqn.bpl +0 -0
  442. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/distributions.bpl +0 -0
  443. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/dyalog.bpl +0 -0
  444. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/graphics.bpl +0 -0
  445. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/lib.bpl +0 -0
  446. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/ngn.bpl +0 -0
  447. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/regex.bpl +0 -0
  448. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/static-int32.bpl +0 -0
  449. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/static.bpl +0 -0
  450. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/april.bpl +0 -0
  451. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/avl.bpl +0 -0
  452. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/redblack.bpl +0 -0
  453. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/sbst.bpl +0 -0
  454. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference/support/splay.bpl +0 -0
  455. {basedpl-0.1.69 → basedpl-0.1.71}/tests/reference.rs +0 -0
  456. {basedpl-0.1.69 → basedpl-0.1.71}/tests/scaling.rs +0 -0
  457. {basedpl-0.1.69 → basedpl-0.1.71}/tests/settings.rs +0 -0
  458. {basedpl-0.1.69 → basedpl-0.1.71}/tests/speed.rs +0 -0
  459. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_api.py +0 -0
  460. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_basic.py +0 -0
  461. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_bpltests.py +0 -0
  462. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_glyphs.py +0 -0
  463. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_graphics.py +0 -0
  464. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_instructions.py +0 -0
  465. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_ipython.py +0 -0
  466. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_kernel.py +0 -0
  467. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_keylayout.py +0 -0
  468. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_notebooks.py +0 -0
  469. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_reference.py +0 -0
  470. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_repl.py +0 -0
  471. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_windows_keyboard.py +0 -0
  472. {basedpl-0.1.69 → basedpl-0.1.71}/tests/test_worker.py +0 -0
  473. {basedpl-0.1.69 → basedpl-0.1.71}/tests/units.rs +0 -0
  474. {basedpl-0.1.69 → basedpl-0.1.71}/tests/wasm.mjs +0 -0
  475. {basedpl-0.1.69 → basedpl-0.1.71}/tests/windows_keyboard.c +0 -0
  476. {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.69"
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.69"
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.69"
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.69"
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.43"
1416
+ version = "0.1.45"
1417
1417
  source = "registry+https://github.com/rust-lang/crates.io-index"
1418
- checksum = "47219acf94cb55feca56bf23818d8d1c013a17fd63023721e6edac118c065629"
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.43"
1437
+ version = "0.1.45"
1438
1438
  source = "registry+https://github.com/rust-lang/crates.io-index"
1439
- checksum = "6991cc5072aadb4af88d88526fb6479f59f9641b86fd6f10b7a407b8e02bd6e4"
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.69"
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.69"
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.43, <1", default-features = false }
15
- rustymath-buffer = ">=0.1.43, <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, with both in cache. `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. 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. On a machine or a size with no entry, the test measures the copy once and prints a line to add to `COPIES`. Each case's line in a log ends with its time for each item, and the log has a copy line for each size of data, which `logged_costs` 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.
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 BPL that makes one call per step of `N`, such as `{⍵}⍣N⊢0`. 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 that only drops a structural result that stays pending, such as large windows or a transpose, counts making the pending copy, not the copy: `{⍵⊣3↕v}⍣N⊢0` retires about 15,000 instructions a step. An indexed write computes the whole array first. `{x←3↕v ⋄ x.[0]←0 ⋄ ⍵}⍣N⊢0` counts the copy itself, about 12 million instructions a step. A join computes at once and needs no write.
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. A review needs each job's full log, because `ghapi`'s `failed_step_log` leaves out the step that prints the CPU model. 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.
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 beside NaN. 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.
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basedpl
3
- Version: 0.1.69
3
+ Version: 0.1.71
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Requires-Dist: fastcore
@@ -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, `X⌊Y` gives NaN or the other argument. `⌊/` and `⌊\` give NaN or the least other item. Which one depends on the processor and the code path. Remove NaNs first where that matters, as in `⌊/(v=v)#v`. Empty reduction identity: `∞`.
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, `X⌈Y` gives NaN or the other argument. `⌈/` and `⌈\` give NaN or the greatest other item. Which one depends on the processor and the code path. Remove NaNs first where that matters, as in `⌈/(v=v)#v`. Empty reduction identity: `¯∞`.
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 one call per step of `N`, such as `{⍵}⍣N⊢0`, or a list of them
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 code, statistics.median((c1-c0)/(n1-n0) for n0, n1, c0, c1 in zip(ns, ns[1:], cs, cs[1:]))
335
- return Instructions(per_step(code) for code in cases)
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
- [(_, per_step)] = instructions(case, setup, bpl=bpl)
379
- samples, total = _self_samples(_program(f'{{⍵⊣({case})}}⍣1000000000⊢0', max(1, round(1e8/per_step)), setup), bpl, seconds)
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 that makes one call per step of `N`, as `instructions` takes, such as `{⍵}⍣N⊢0`
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 that makes one call per step of `N`, as `instructions` takes, such as `{⍵}⍣N⊢0`
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
- _CPU = re.compile(r'^(?:\S*Z )?model name\s*:\s*(.+?)\s*$', re.M)
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 model that the log reports 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."
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
- cpu = c[1] if (c := _CPU.search(log)) else None
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 _COPY.finditer(log)))
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 cost moved by more than `CHANGE`, largest change first. `before` is `None` for a case that the earlier run didn't time, and `budget` is `None` for a case with no budget."
509
- def __repr__(self): return '\n'.join(f'{_or_dash(b, ".2f"):>9} {a:>9.2f} {_or_dash(b and a/b-1, "+.0%"):>6} {_or_dash(budget, "g"):>9} {"over" if _over(a, budget) else "":4} {s} {c}' for s, c, b, a, budget in self)
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 cost moved by more than `CHANGE` from `before` in either direction."
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 = [(s, c, old.get((s, c)), cost, budget) for s, c, cost, budget in after]
520
- def change(row): return abs(row[3]/row[2] - 1) if row[2] else 0
521
- return CostChanges(sorted((r for r in rows if _over(r[3], r[4]) or change(r) > CHANGE), key=lambda r: (not _over(r[3], r[4]), -change(r))))
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, function: &Function, left: Option<&Value>, right: &Value) -> Result<Bound, Error> {
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) = (function.clone(), left.cloned(), right.clone());
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, &return_span)
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 beside any number gives NaN, at the width the two compute at. An exact number stays exact beside an infinity, and beside another exact number.
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: 0 for macOS, 1 for Linux, 2 for the browser build. `basedpl.bpltests.logged_costs` reads these lines.
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: its section, code, budgets and cost.
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
- let cases = costs(spec["speed"].as_str().ok_or("costs needs speed as a string")?, only.as_deref(), interrupt)?;
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 or NaN. On a large array they don't fuse beside an exact number or a comparison, because a fused graph reads them as floats
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]ₓ