rootfig 0.8.0__tar.gz → 0.9.0__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 (347) hide show
  1. {rootfig-0.8.0 → rootfig-0.9.0}/CONTRIBUTING.md +31 -6
  2. {rootfig-0.8.0 → rootfig-0.9.0}/PKG-INFO +1 -1
  3. {rootfig-0.8.0 → rootfig-0.9.0}/docs/composable.md +1 -1
  4. {rootfig-0.8.0 → rootfig-0.9.0}/docs/ecosystem.md +4 -3
  5. {rootfig-0.8.0 → rootfig-0.9.0}/docs/expressions.md +4 -1
  6. {rootfig-0.8.0 → rootfig-0.9.0}/docs/plotting.md +28 -0
  7. {rootfig-0.8.0 → rootfig-0.9.0}/pyproject.toml +3 -2
  8. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/__init__.py +1 -1
  9. rootfig-0.9.0/src/rootfig/_threads.py +65 -0
  10. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/pipeline.py +203 -61
  11. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/stats.py +17 -6
  12. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/__init__.py +2 -0
  13. rootfig-0.9.0/src/rootfig/io/compat.py +51 -0
  14. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/sources.py +203 -37
  15. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/binning.py +131 -28
  16. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/selection/__init__.py +3 -0
  17. rootfig-0.9.0/src/rootfig/selection/chunks.py +148 -0
  18. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/selection/columns.py +67 -11
  19. {rootfig-0.8.0 → rootfig-0.9.0}/tests/conftest.py +11 -5
  20. rootfig-0.9.0/tests/data/embedded_basket.root +0 -0
  21. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_api.py +4 -4
  22. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_discovery.py +1 -0
  23. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_histograms.py +266 -0
  24. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_io.py +265 -0
  25. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_model.py +31 -0
  26. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_selection.py +185 -0
  27. {rootfig-0.8.0 → rootfig-0.9.0}/.gitignore +0 -0
  28. {rootfig-0.8.0 → rootfig-0.9.0}/LICENSE +0 -0
  29. {rootfig-0.8.0 → rootfig-0.9.0}/README.md +0 -0
  30. {rootfig-0.8.0 → rootfig-0.9.0}/docs/api.md +0 -0
  31. {rootfig-0.8.0 → rootfig-0.9.0}/docs/batch.md +0 -0
  32. {rootfig-0.8.0 → rootfig-0.9.0}/docs/gallery/index.md +0 -0
  33. {rootfig-0.8.0 → rootfig-0.9.0}/docs/hooks/gallery.py +0 -0
  34. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-alice-dark.png +0 -0
  35. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-alice.png +0 -0
  36. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-atlas-dark.png +0 -0
  37. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-atlas.png +0 -0
  38. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-cms-dark.png +0 -0
  39. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-cms.png +0 -0
  40. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dark.png +0 -0
  41. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dune-dark.png +0 -0
  42. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dune.png +0 -0
  43. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-lhcb-dark.png +0 -0
  44. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-lhcb.png +0 -0
  45. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays.png +0 -0
  46. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-alice-dark.png +0 -0
  47. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-alice.png +0 -0
  48. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-atlas-dark.png +0 -0
  49. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-atlas.png +0 -0
  50. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-cms-dark.png +0 -0
  51. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-cms.png +0 -0
  52. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dark.png +0 -0
  53. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dune-dark.png +0 -0
  54. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dune.png +0 -0
  55. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-lhcb-dark.png +0 -0
  56. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-lhcb.png +0 -0
  57. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation.png +0 -0
  58. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-alice-dark.png +0 -0
  59. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-alice.png +0 -0
  60. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-atlas-dark.png +0 -0
  61. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-atlas.png +0 -0
  62. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-cms-dark.png +0 -0
  63. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-cms.png +0 -0
  64. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dark.png +0 -0
  65. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dune-dark.png +0 -0
  66. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dune.png +0 -0
  67. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-lhcb-dark.png +0 -0
  68. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-lhcb.png +0 -0
  69. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow.png +0 -0
  70. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-alice-dark.png +0 -0
  71. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-alice.png +0 -0
  72. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-atlas-dark.png +0 -0
  73. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-atlas.png +0 -0
  74. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-cms-dark.png +0 -0
  75. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-cms.png +0 -0
  76. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dark.png +0 -0
  77. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dune-dark.png +0 -0
  78. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dune.png +0 -0
  79. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-lhcb-dark.png +0 -0
  80. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-lhcb.png +0 -0
  81. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency.png +0 -0
  82. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-alice-dark.png +0 -0
  83. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-alice.png +0 -0
  84. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-atlas-dark.png +0 -0
  85. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-atlas.png +0 -0
  86. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-cms-dark.png +0 -0
  87. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-cms.png +0 -0
  88. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dark.png +0 -0
  89. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dune-dark.png +0 -0
  90. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dune.png +0 -0
  91. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-lhcb-dark.png +0 -0
  92. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-lhcb.png +0 -0
  93. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions.png +0 -0
  94. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-alice-dark.png +0 -0
  95. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-alice.png +0 -0
  96. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-atlas-dark.png +0 -0
  97. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-atlas.png +0 -0
  98. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-cms-dark.png +0 -0
  99. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-cms.png +0 -0
  100. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dark.png +0 -0
  101. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dune-dark.png +0 -0
  102. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dune.png +0 -0
  103. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-lhcb-dark.png +0 -0
  104. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-lhcb.png +0 -0
  105. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats.png +0 -0
  106. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-alice-dark.png +0 -0
  107. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-alice.png +0 -0
  108. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-atlas-dark.png +0 -0
  109. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-atlas.png +0 -0
  110. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-cms-dark.png +0 -0
  111. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-cms.png +0 -0
  112. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dark.png +0 -0
  113. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dune-dark.png +0 -0
  114. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dune.png +0 -0
  115. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-lhcb-dark.png +0 -0
  116. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-lhcb.png +0 -0
  117. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d.png +0 -0
  118. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-alice-dark.png +0 -0
  119. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-alice.png +0 -0
  120. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-atlas-dark.png +0 -0
  121. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-atlas.png +0 -0
  122. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-cms-dark.png +0 -0
  123. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-cms.png +0 -0
  124. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dark.png +0 -0
  125. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dune-dark.png +0 -0
  126. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dune.png +0 -0
  127. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-lhcb-dark.png +0 -0
  128. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-lhcb.png +0 -0
  129. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes.png +0 -0
  130. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/luminosity-dark.png +0 -0
  131. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/luminosity.png +0 -0
  132. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/many_plots-dark.png +0 -0
  133. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/many_plots.png +0 -0
  134. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-alice-dark.png +0 -0
  135. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-alice.png +0 -0
  136. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-atlas-dark.png +0 -0
  137. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-atlas.png +0 -0
  138. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-cms-dark.png +0 -0
  139. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-cms.png +0 -0
  140. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dark.png +0 -0
  141. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dune-dark.png +0 -0
  142. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dune.png +0 -0
  143. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-lhcb-dark.png +0 -0
  144. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-lhcb.png +0 -0
  145. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event.png +0 -0
  146. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-alice-dark.png +0 -0
  147. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-alice.png +0 -0
  148. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-atlas-dark.png +0 -0
  149. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-atlas.png +0 -0
  150. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-cms-dark.png +0 -0
  151. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-cms.png +0 -0
  152. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dark.png +0 -0
  153. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dune-dark.png +0 -0
  154. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dune.png +0 -0
  155. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-lhcb-dark.png +0 -0
  156. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-lhcb.png +0 -0
  157. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio.png +0 -0
  158. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-alice-dark.png +0 -0
  159. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-alice.png +0 -0
  160. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-atlas-dark.png +0 -0
  161. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-atlas.png +0 -0
  162. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-cms-dark.png +0 -0
  163. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-cms.png +0 -0
  164. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dark.png +0 -0
  165. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dune-dark.png +0 -0
  166. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dune.png +0 -0
  167. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-lhcb-dark.png +0 -0
  168. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-lhcb.png +0 -0
  169. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile.png +0 -0
  170. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-alice-dark.png +0 -0
  171. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-alice.png +0 -0
  172. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-atlas-dark.png +0 -0
  173. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-atlas.png +0 -0
  174. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-cms-dark.png +0 -0
  175. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-cms.png +0 -0
  176. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dark.png +0 -0
  177. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dune-dark.png +0 -0
  178. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dune.png +0 -0
  179. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-lhcb-dark.png +0 -0
  180. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-lhcb.png +0 -0
  181. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull.png +0 -0
  182. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-alice-dark.png +0 -0
  183. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-alice.png +0 -0
  184. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-atlas-dark.png +0 -0
  185. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-atlas.png +0 -0
  186. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-cms-dark.png +0 -0
  187. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-cms.png +0 -0
  188. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dark.png +0 -0
  189. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dune-dark.png +0 -0
  190. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dune.png +0 -0
  191. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-lhcb-dark.png +0 -0
  192. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-lhcb.png +0 -0
  193. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick.png +0 -0
  194. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-alice-dark.png +0 -0
  195. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-alice.png +0 -0
  196. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-atlas-dark.png +0 -0
  197. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-atlas.png +0 -0
  198. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-cms-dark.png +0 -0
  199. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-cms.png +0 -0
  200. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dark.png +0 -0
  201. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dune-dark.png +0 -0
  202. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dune.png +0 -0
  203. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-lhcb-dark.png +0 -0
  204. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-lhcb.png +0 -0
  205. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference.png +0 -0
  206. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/robust_range-dark.png +0 -0
  207. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/robust_range.png +0 -0
  208. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-alice-dark.png +0 -0
  209. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-alice.png +0 -0
  210. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-atlas-dark.png +0 -0
  211. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-atlas.png +0 -0
  212. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-cms-dark.png +0 -0
  213. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-cms.png +0 -0
  214. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dark.png +0 -0
  215. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dune-dark.png +0 -0
  216. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dune.png +0 -0
  217. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-lhcb-dark.png +0 -0
  218. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-lhcb.png +0 -0
  219. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack.png +0 -0
  220. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-alice-dark.png +0 -0
  221. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-alice.png +0 -0
  222. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-atlas-dark.png +0 -0
  223. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-atlas.png +0 -0
  224. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-cms-dark.png +0 -0
  225. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-cms.png +0 -0
  226. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dark.png +0 -0
  227. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dune-dark.png +0 -0
  228. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dune.png +0 -0
  229. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-lhcb-dark.png +0 -0
  230. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-lhcb.png +0 -0
  231. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data.png +0 -0
  232. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stored_histograms-dark.png +0 -0
  233. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stored_histograms.png +0 -0
  234. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/style_colors-dark.png +0 -0
  235. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/style_colors.png +0 -0
  236. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-alice-dark.png +0 -0
  237. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-alice.png +0 -0
  238. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-atlas-dark.png +0 -0
  239. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-atlas.png +0 -0
  240. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-cms-dark.png +0 -0
  241. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-cms.png +0 -0
  242. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dark.png +0 -0
  243. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dune-dark.png +0 -0
  244. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dune.png +0 -0
  245. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-lhcb-dark.png +0 -0
  246. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-lhcb.png +0 -0
  247. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics.png +0 -0
  248. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-alice-dark.png +0 -0
  249. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-alice.png +0 -0
  250. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-atlas-dark.png +0 -0
  251. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-atlas.png +0 -0
  252. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-cms-dark.png +0 -0
  253. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-cms.png +0 -0
  254. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dark.png +0 -0
  255. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dune-dark.png +0 -0
  256. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dune.png +0 -0
  257. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-lhcb-dark.png +0 -0
  258. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-lhcb.png +0 -0
  259. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins.png +0 -0
  260. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-alice-dark.png +0 -0
  261. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-alice.png +0 -0
  262. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-atlas-dark.png +0 -0
  263. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-atlas.png +0 -0
  264. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-cms-dark.png +0 -0
  265. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-cms.png +0 -0
  266. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dark.png +0 -0
  267. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dune-dark.png +0 -0
  268. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dune.png +0 -0
  269. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-lhcb-dark.png +0 -0
  270. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-lhcb.png +0 -0
  271. {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio.png +0 -0
  272. {rootfig-0.8.0 → rootfig-0.9.0}/docs/index.md +0 -0
  273. {rootfig-0.8.0 → rootfig-0.9.0}/docs/quickstart.md +0 -0
  274. {rootfig-0.8.0 → rootfig-0.9.0}/docs/stylesheets/gallery.css +0 -0
  275. {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/__init__.py +0 -0
  276. {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/__main__.py +0 -0
  277. {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/data.py +0 -0
  278. {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/registry.py +0 -0
  279. {rootfig-0.8.0 → rootfig-0.9.0}/mkdocs.yml +0 -0
  280. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_mapping.py +0 -0
  281. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_storage.py +0 -0
  282. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_typing.py +0 -0
  283. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/__init__.py +0 -0
  284. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_common.py +0 -0
  285. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_discover.py +0 -0
  286. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_hists.py +0 -0
  287. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_panel.py +0 -0
  288. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_pdf.py +0 -0
  289. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/batch.py +0 -0
  290. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/data.py +0 -0
  291. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/measures.py +0 -0
  292. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/plots1d.py +0 -0
  293. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/plots2d.py +0 -0
  294. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/tables.py +0 -0
  295. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/errors.py +0 -0
  296. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/__init__.py +0 -0
  297. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/functions.py +0 -0
  298. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/parser.py +0 -0
  299. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/__init__.py +0 -0
  300. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/build.py +0 -0
  301. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/comparison.py +0 -0
  302. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/cutflow.py +0 -0
  303. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/efficiency.py +0 -0
  304. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/groups.py +0 -0
  305. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/normalize.py +0 -0
  306. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/prefetch.py +0 -0
  307. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/sources.py +0 -0
  308. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/stored.py +0 -0
  309. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/systematics.py +0 -0
  310. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/cache.py +0 -0
  311. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/objects.py +0 -0
  312. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/schema.py +0 -0
  313. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/__init__.py +0 -0
  314. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/cuts.py +0 -0
  315. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/filenames.py +0 -0
  316. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/groups.py +0 -0
  317. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/inputs.py +0 -0
  318. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/samples.py +0 -0
  319. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/style.py +0 -0
  320. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/systematics.py +0 -0
  321. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/units.py +0 -0
  322. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/variables.py +0 -0
  323. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/__init__.py +0 -0
  324. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/annotations.py +0 -0
  325. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/correlation.py +0 -0
  326. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/engine.py +0 -0
  327. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/figure.py +0 -0
  328. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/finish.py +0 -0
  329. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/hist1d.py +0 -0
  330. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/hist2d.py +0 -0
  331. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/pages.py +0 -0
  332. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/panel.py +0 -0
  333. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/points.py +0 -0
  334. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/result.py +0 -0
  335. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/style.py +0 -0
  336. {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/py.typed +0 -0
  337. {rootfig-0.8.0 → rootfig-0.9.0}/tests/data/split_collection.root +0 -0
  338. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_batch.py +0 -0
  339. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_expressions.py +0 -0
  340. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_gallery.py +0 -0
  341. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_pages.py +0 -0
  342. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_plotting.py +0 -0
  343. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_save_pdf.py +0 -0
  344. {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_tutorials.py +0 -0
  345. {rootfig-0.8.0 → rootfig-0.9.0}/tests/type_checks/batch.py +0 -0
  346. {rootfig-0.8.0 → rootfig-0.9.0}/tests/type_checks/groups.py +0 -0
  347. {rootfig-0.8.0 → rootfig-0.9.0}/tests/type_checks/systematics.py +0 -0
@@ -62,12 +62,12 @@ docstrings and full type hints.
62
62
 
63
63
  ## Test data
64
64
 
65
- Almost all tests generate their ROOT files with uproot on the fly. The one
66
- committed file, `tests/data/split_collection.root`, has a split
67
- `std::vector<struct>` branch and a `TParameter` the way podio/EDM4hep and
68
- FCCAnalyses write them. uproot cannot write such a file, so it was produced
69
- with PyROOT (ROOT 6.40) by this script; rerun it only if the layout has to
70
- change, and keep the file small:
65
+ Almost all tests generate their ROOT files with uproot on the fly. Two files
66
+ that uproot cannot write are committed, produced with ROOT 6.40; rerun their
67
+ scripts only if the layout has to change, and keep the files small.
68
+
69
+ `tests/data/split_collection.root` has a split `std::vector<struct>` branch and
70
+ a `TParameter` the way podio/EDM4hep and FCCAnalyses write them (PyROOT):
71
71
 
72
72
  ```python
73
73
  import ROOT
@@ -95,6 +95,31 @@ f.Write()
95
95
  f.Close()
96
96
  ```
97
97
 
98
+ `tests/data/embedded_basket.root` holds a tree whose baskets are embedded in its
99
+ branches, without keys of their own, as a tree filled in memory and written
100
+ afterwards keeps them (`root -l -b -q embedded.C`):
101
+
102
+ ```cpp
103
+ // embedded.C
104
+ void embedded() {
105
+ TTree t("events", "events");
106
+ t.SetDirectory(nullptr); // in memory: nothing is flushed while filling
107
+ Float_t x;
108
+ std::vector<double> y;
109
+ t.Branch("x", &x);
110
+ t.Branch("y", &y);
111
+ TRandom3 r(1);
112
+ for (int i = 0; i < 500; ++i) {
113
+ x = r.Gaus(0, 1);
114
+ y.assign(i % 4, r.Uniform());
115
+ t.Fill();
116
+ }
117
+ TFile f("embedded_basket.root", "RECREATE");
118
+ f.WriteObject(&t, "events");
119
+ f.Close();
120
+ }
121
+ ```
122
+
98
123
  ## Figures and the gallery
99
124
 
100
125
  The `examples/gallery` package is both the showcase and the image-regression
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rootfig
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Publication-quality figures straight from ROOT trees and histograms, without ROOT: uproot + Awkward + hist + mplhep with a TTree::Draw-like API.
5
5
  Project-URL: Homepage, https://github.com/jbeirer/rootfig
6
6
  Project-URL: Documentation, https://jbeirer.github.io/rootfig/
@@ -52,7 +52,7 @@ mem = rf.Sample({"x": awkward_array, "w": weights}, label="in memory")
52
52
  trees; naming the histogram in place of the branch reads it (see
53
53
  [stored histograms](plotting.md#histograms-already-in-root-files)).
54
54
  - `entry_start`/`entry_stop` restrict reading for quick looks at large files
55
- (a plot reads every needed branch of every file into memory at once). For a
55
+ (a plot reads every file of the sample, a chunk of entries at a time). For a
56
56
  ready-made `FileSource`, give the range to the source itself; passing it to
57
57
  `Sample` afterwards raises a [`SourceError`][rootfig.SourceError].
58
58
  - `systematics={name: variation}` lists the sample's sources of systematic
@@ -72,8 +72,9 @@ can discover shared histograms with `rf.ALL`. See the
72
72
  goodness-of-fit panel): [plothist](https://plothist.readthedocs.io).
73
73
  - You need a full columnar analysis framework with lazy, distributed
74
74
  processing: [coffea](https://coffeateam.github.io/coffea/). rootfig reads
75
- every branch a plot needs from every file into memory at once (only the
76
- required branches, optionally an entry range), which suits quick looks and
77
- moderate ntuples, not multi-terabyte datasets.
75
+ the branches a plot needs a chunk of entries at a time, in threads, and holds
76
+ the values it fills (see [large inputs](plotting.md#large-inputs)), all in one
77
+ process: it suits quick looks and ntuples that one machine reads in minutes,
78
+ not multi-terabyte datasets.
78
79
  - You want to build fit templates and workspaces: [cabinetry](https://cabinetry.readthedocs.io).
79
80
  - You want a quick terminal look at a branch: [histoprint](https://github.com/scikit-hep/histoprint).
@@ -120,7 +120,10 @@ weights (`hist` `Weight` storage).
120
120
  ## Missing and non-finite values
121
121
 
122
122
  `None` values (e.g. `max(Muon_pt)` of an event with no muons) never enter a
123
- histogram; in a selection they count as `False`. `nan` and `inf` values are
123
+ histogram; in a selection they count as `False`. A whole collection that is
124
+ `None` for an event, whether the variable's, the weight's or the one a
125
+ per-object selection is evaluated on, drops that event, and its objects are
126
+ counted as missing. `nan` and `inf` values are
124
127
  dropped with a [`RootfigWarning`][rootfig.RootfigWarning] telling you how
125
128
  many, or raise with `nonfinite="error"`. Both counts are recorded in the
126
129
  histogram's `stats`.
@@ -466,6 +466,34 @@ panel alike.
466
466
  is not needed. End the call with `;` to hide the `Plot` repr, and use
467
467
  `Plot.close()` in loops that make many figures.
468
468
 
469
+ ## Large inputs
470
+
471
+ A plot reads only the branches its variable, selection, weight and systematics
472
+ use, a chunk of entries (about 32 MB of arrays) at a time. The chunks are
473
+ decompressed and prepared (expressions, selection, weights) in parallel
474
+ threads, and only the values that fill the histograms are kept, so the peak
475
+ memory follows those values rather than every branch read. The histograms and
476
+ their statistics are the same as from reading everything at once.
477
+
478
+ - Decompressing and preparing share up to eight worker threads, fewer on a
479
+ machine with fewer cores. Set `ROOTFIG_THREADS` to choose the number, e.g. the
480
+ cores a batch job was given; `ROOTFIG_THREADS=1` decompresses and prepares in
481
+ the calling thread. uproot fetches the file contents in a thread or two of its
482
+ own either way, which do little but wait for the data.
483
+ - An explicit range (`bins=(50, 0, 200)`, or `range=(low, high)`) skips range
484
+ inference, which takes two medians over all the values of every sample.
485
+ - Many plots of the same files are fastest as a [`PlotBook`](batch.md), which
486
+ reads each file once per batch of variables: a tree with thousands of
487
+ branches costs a noticeable fraction of a second to open, every time.
488
+ - LZMA-compressed files (the NanoAOD default) take several times longer to
489
+ decompress than ZLIB, LZ4 or ZSTD ones; the threads help most there.
490
+ - A sample whose files hold a branch in different types (`float32` in one,
491
+ `float64` in another) is read whole once the difference shows, and evaluated
492
+ in the wider type. The first file's chunks may already have been evaluated in
493
+ their own type by then, so NumPy can warn about an overflow that the wider
494
+ type avoids. The histograms are the same either way, also when NumPy is set
495
+ to raise such errors (`np.errstate(over="raise")`, a warnings filter).
496
+
469
497
  ## 2D histograms and correlations
470
498
 
471
499
  ```python
@@ -29,8 +29,9 @@ classifiers = [
29
29
  # Floors are the oldest versions the test suite passes with (CI job "minimum-versions"),
30
30
  # kept at or below what the Key4hep nightlies ship (CI workflow "key4hep"), which take
31
31
  # every dependency from the stack: uproot 5.7.1 reads nested RNTuple fields and entry
32
- # ranges across files; mplhep 1.2.1 has exp_label(text=) and hist2dplot(mask=); hist 2.9
33
- # exposes the axis metadata stored histograms are renamed through.
32
+ # ranges across files (a floor of 5.7.3 deletes src/rootfig/io/compat.py, see there);
33
+ # mplhep 1.2.1 has exp_label(text=) and hist2dplot(mask=); hist 2.9 exposes the axis
34
+ # metadata stored histograms are renamed through.
34
35
  dependencies = [
35
36
  "uproot>=5.7.1",
36
37
  "awkward>=2.8",
@@ -58,7 +58,7 @@ from rootfig.histograms import (
58
58
  from rootfig.model import Cut, Group, Sample, Style, Systematic, Variable, log_bins
59
59
  from rootfig.plotting import Plot, dark_theme, use_style
60
60
 
61
- __version__ = "0.8.0"
61
+ __version__ = "0.9.0"
62
62
 
63
63
  __all__ = [
64
64
  "ALL",
@@ -0,0 +1,65 @@
1
+ """How many threads rootfig reads and prepares event data in, and the pool they share."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Iterator
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from contextlib import contextmanager
9
+ from contextvars import ContextVar
10
+
11
+ __all__ = ["THREADS", "worker_pool"]
12
+
13
+
14
+ def _threads() -> int:
15
+ given = os.environ.get("ROOTFIG_THREADS")
16
+ if given is not None:
17
+ try:
18
+ return max(1, int(given))
19
+ except ValueError:
20
+ msg = f"ROOTFIG_THREADS must be a whole number of threads, got {given!r}"
21
+ raise ValueError(msg) from None
22
+ available = (
23
+ len(os.sched_getaffinity(0)) if hasattr(os, "sched_getaffinity") else os.cpu_count() or 1
24
+ )
25
+ return min(8, available)
26
+
27
+
28
+ THREADS: int = _threads()
29
+ """Worker threads that decompress baskets and prepare chunks of events, together.
30
+
31
+ Both release the GIL for most of their work, so a few threads make reading and
32
+ preparing several times faster; more add little and would crowd a shared
33
+ machine. ``ROOTFIG_THREADS`` sets the number (``1`` decompresses and prepares in
34
+ the calling thread), for instance to match the cores a batch job was given.
35
+ uproot fetches the file contents in threads of its own either way.
36
+ """
37
+
38
+ _LENT: ContextVar[ThreadPoolExecutor | None] = ContextVar("rootfig_worker_pool", default=None)
39
+
40
+
41
+ @contextmanager
42
+ def worker_pool(*, lend: bool = False) -> Iterator[ThreadPoolExecutor | None]:
43
+ """Give the block :data:`THREADS` worker threads; ``None`` for one (the calling thread).
44
+
45
+ With ``lend``, every block inside takes this pool instead of starting its own:
46
+ chunks are read while earlier ones are prepared, and one pool keeps both to
47
+ :data:`THREADS` threads together. A pool lives for its block only, so no thread
48
+ outlives it (and none is inherited, dead, by a forked process). Only a plain
49
+ function may lend: a generator would lend to its caller between its yields.
50
+ """
51
+ if THREADS == 1:
52
+ yield None
53
+ return
54
+ lent = _LENT.get()
55
+ if lent is not None:
56
+ yield lent
57
+ return
58
+ pool = ThreadPoolExecutor(max_workers=THREADS)
59
+ token = _LENT.set(pool) if lend else None
60
+ try:
61
+ yield pool
62
+ finally:
63
+ if token is not None:
64
+ _LENT.reset(token)
65
+ pool.shutdown(cancel_futures=True)
@@ -10,9 +10,10 @@ of their histograms.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- from collections.abc import Mapping, Sequence
13
+ from collections.abc import Iterator, Mapping, Sequence
14
14
  from contextlib import AbstractContextManager
15
15
  from dataclasses import dataclass
16
+ from itertools import pairwise
16
17
  from os import PathLike
17
18
  from typing import Any
18
19
 
@@ -20,14 +21,20 @@ import awkward as ak
20
21
  import numpy as np
21
22
 
22
23
  from rootfig._typing import Hist
23
- from rootfig.errors import MissingBranchError, SourceError, SystematicError, annotate
24
+ from rootfig.errors import (
25
+ MissingBranchError,
26
+ RootfigError,
27
+ SourceError,
28
+ SystematicError,
29
+ annotate,
30
+ )
24
31
  from rootfig.expressions import Expression, parse
25
32
  from rootfig.histograms.build import Histogram, fill, from_sample, mirror
26
33
  from rootfig.histograms.groups import regroup_histograms
27
34
  from rootfig.histograms.sources import shared_source
28
35
  from rootfig.histograms.stats import summarize
29
36
  from rootfig.histograms.stored import read_stored, stored_mode
30
- from rootfig.io import ArraySource, FileSource, ReadCache, Source, as_source
37
+ from rootfig.io import CHUNK_BYTES, ArraySource, FileSource, ReadCache, Source, as_source
31
38
  from rootfig.io.sources import resolve_files
32
39
  from rootfig.model.binning import Axis, resolve_axis
33
40
  from rootfig.model.cuts import Cut, CutLike, as_cut
@@ -36,7 +43,7 @@ from rootfig.model.inputs import leaf_samples
36
43
  from rootfig.model.samples import Sample
37
44
  from rootfig.model.systematics import Systematic, SystematicLike, as_systematics
38
45
  from rootfig.model.variables import Variable, as_variable
39
- from rootfig.selection import Columns, NonFinitePolicy, prepare
46
+ from rootfig.selection import Columns, NonFinitePolicy, Request, prepare_chunks
40
47
 
41
48
  __all__ = [
42
49
  "branch_names",
@@ -47,6 +54,7 @@ __all__ = [
47
54
  "load_columns",
48
55
  "load_columns_each",
49
56
  "read_arrays",
57
+ "read_chunks",
50
58
  ]
51
59
 
52
60
 
@@ -102,6 +110,143 @@ def read_arrays(
102
110
  return arrays, source_length(source)
103
111
 
104
112
 
113
+ def read_chunks(
114
+ sample: Sample, expressions: Sequence[Any], *, cache: ReadCache | None = None
115
+ ) -> Iterator[tuple[dict[str, Any], int]]:
116
+ """Return the branches ``expressions`` need from ``sample``, a chunk of events at a time.
117
+
118
+ Each chunk comes with its number of events, and the chunks, joined in order,
119
+ are what :func:`read_arrays` returns. Files are read chunk by chunk
120
+ (:meth:`~rootfig.io.FileSource.iterate`), so only the chunks being prepared
121
+ are held, never all branches of all events; arrays already in memory (in-memory
122
+ data, a ``cache``) are cut into slices of about as many bytes, views that copy
123
+ nothing, so that they are prepared in parallel alike. The branch names are
124
+ checked before this returns, and nothing is read until the first chunk is
125
+ asked for. A chunk holds each branch in the type its file does, which the
126
+ whole read promotes to one type for all files; a chunk whose types differ from
127
+ the first chunk's raises :class:`_MixedTypesError` (see :func:`_prepared`).
128
+ """
129
+ source = sample.source
130
+ if cache is None and isinstance(source, FileSource):
131
+ needed = branch_names(source, expressions)
132
+ if needed:
133
+ return _same_types(source.iterate(needed))
134
+ arrays, n_events = read_arrays(sample, expressions, cache=cache)
135
+ return _slices(arrays, n_events)
136
+
137
+
138
+ class _MixedTypesError(Exception):
139
+ """A chunk holds a branch in another type than the first chunk did."""
140
+
141
+
142
+ def _same_types(chunks: Iterator[dict[str, Any]]) -> Iterator[tuple[dict[str, Any], int]]:
143
+ """Yield ``chunks`` with their numbers of events, as long as their types stay the first's."""
144
+ try:
145
+ first = None
146
+ for arrays in chunks:
147
+ types = [array.type.content for array in arrays.values()]
148
+ if first is None:
149
+ first = types
150
+ elif types != first:
151
+ raise _MixedTypesError
152
+ yield arrays, len(next(iter(arrays.values())))
153
+ finally:
154
+ close = getattr(chunks, "close", None)
155
+ if close is not None:
156
+ close()
157
+
158
+
159
+ def _prepared(
160
+ sample: Sample,
161
+ expressions: Sequence[Any],
162
+ requests: Sequence[Request],
163
+ *,
164
+ cache: ReadCache | None = None,
165
+ ) -> list[Columns]:
166
+ """Run ``requests`` on the chunks of ``sample`` that :func:`read_chunks` reads.
167
+
168
+ Where the files hold a branch in different types (``int32`` in one, ``int64``
169
+ in another), the sample is read whole instead (:func:`read_arrays`), which
170
+ promotes them to one type before anything is evaluated: ``x * x`` would
171
+ otherwise overflow in the ``int32`` file's chunks, and the result would depend
172
+ on where the chunks start and on whether a cache was used. The difference shows
173
+ at the first chunk of another type, so the chunks before it have been prepared
174
+ in their own type. Their columns are discarded, and an error they raise that
175
+ rootfig did not raise itself (NumPy's under ``np.errstate``, a warning turned
176
+ into an error, a NumPy error handler's, Awkward's) sends the sample to the whole
177
+ read as well if its files' types differ, which is only then looked up: the
178
+ sample fails or succeeds as the whole read does. Running out of memory does not,
179
+ since the whole read takes more. Their warnings are not taken back. Knowing
180
+ every file's types beforehand would cost opening each file's metadata once
181
+ more, a quarter of a second for a tree of 1 000 branches.
182
+ """
183
+ try:
184
+ return prepare_chunks(read_chunks(sample, expressions, cache=cache), requests)
185
+ except _MixedTypesError:
186
+ pass
187
+ except Exception as exc:
188
+ chain = list(_chain(exc))
189
+ if (
190
+ cache is not None
191
+ or isinstance(exc, SourceError)
192
+ or all(isinstance(each, RootfigError) for each in chain)
193
+ or any(isinstance(each, MemoryError) for each in chain) # a whole read needs more
194
+ ):
195
+ raise
196
+ try:
197
+ differ = _types_differ(sample, expressions)
198
+ except Exception: # a file that cannot tell: the error stands as raised
199
+ differ = False
200
+ if not differ:
201
+ raise
202
+ return prepare_chunks(_slices(*read_arrays(sample, expressions, cache=cache)), requests)
203
+
204
+
205
+ def _chain(exc: BaseException) -> Iterator[BaseException]:
206
+ """Yield ``exc`` and the exceptions it was raised from or while handling, once each."""
207
+ seen: set[int] = set()
208
+ cause: BaseException | None = exc
209
+ while cause is not None and id(cause) not in seen:
210
+ yield cause
211
+ seen.add(id(cause))
212
+ cause = cause.__cause__ or cause.__context__
213
+
214
+
215
+ def _types_differ(sample: Sample, expressions: Sequence[Any]) -> bool:
216
+ """Return whether the files of ``sample`` hold the branches read in different types.
217
+
218
+ The types come from each file's metadata (:meth:`~rootfig.io.FileSource.branch_forms`),
219
+ so an empty tree has them too.
220
+ """
221
+ source = sample.source
222
+ if not isinstance(source, FileSource) or len(source.files) < 2:
223
+ return False
224
+ needed = branch_names(source, expressions)
225
+ tree = source.resolved_tree()
226
+
227
+ def types(path: str) -> list[Any]:
228
+ forms = FileSource(path, tree).branch_forms()
229
+ return [forms[name].type if name in forms else None for name in needed]
230
+
231
+ first = types(source.files[0])
232
+ return any(types(path) != first for path in source.files[1:])
233
+
234
+
235
+ def _slices(arrays: dict[str, Any], n_events: int) -> Iterator[tuple[dict[str, Any], int]]:
236
+ """Cut ``arrays`` into consecutive slices of at most about :data:`~rootfig.io.CHUNK_BYTES`.
237
+
238
+ With nothing read (every expression a constant), each event still becomes a
239
+ ``float64`` once a constant is broadcast to it, and those are what is counted.
240
+ """
241
+ size = sum(getattr(array, "nbytes", 0) for array in arrays.values()) or 8 * n_events
242
+ count = max(1, min(n_events, -(-size // CHUNK_BYTES)))
243
+ if count == 1:
244
+ yield arrays, n_events
245
+ return
246
+ for start, stop in pairwise(n_events * part // count for part in range(count + 1)):
247
+ yield {name: array[start:stop] for name, array in arrays.items()}, stop - start
248
+
249
+
105
250
  def branch_names(source: Source, expressions: Sequence[Any]) -> list[str]:
106
251
  """Return the branches of ``source`` that ``expressions`` need, once each, in order of use.
107
252
 
@@ -154,17 +299,16 @@ def load_columns(
154
299
  var_exprs = [as_variable(v).expression for v in variables]
155
300
  cut = combined_selection(sample, selection)
156
301
  weight_expr = combined_weight(sample, weight)
157
- arrays, n_events = _read_for(sample, var_exprs, cut, weight_expr, cache=cache)
158
- return prepare(
159
- arrays,
160
- var_exprs,
302
+ request = Request(
303
+ tuple(var_exprs),
161
304
  selection=None if cut is None else cut.expression,
162
305
  weight=weight_expr,
163
306
  scale=sample.scale * sample.lumi_scale(lumi),
164
307
  nonfinite=nonfinite,
165
308
  context=sample.label,
166
- n_events=n_events,
167
309
  )
310
+ expressions = _expressions(var_exprs, cut, weight_expr)
311
+ return _prepared(sample, expressions, [request], cache=cache)[0]
168
312
 
169
313
 
170
314
  def load_columns_each(
@@ -186,38 +330,31 @@ def load_columns_each(
186
330
  var_exprs = [as_variable(v).expression for v in variables]
187
331
  cut = combined_selection(sample, selection)
188
332
  weight_expr = combined_weight(sample, weight)
189
- arrays, n_events = _read_for(sample, var_exprs, cut, weight_expr)
190
333
  scale = sample.scale * sample.lumi_scale(lumi)
191
- return [
192
- prepare(
193
- arrays,
194
- [expression],
334
+ requests = [
335
+ Request(
336
+ (expression,),
195
337
  selection=None if cut is None else cut.expression,
196
338
  weight=weight_expr,
197
339
  scale=scale,
198
340
  nonfinite=nonfinite,
199
341
  context=sample.label,
200
- n_events=n_events,
201
342
  )
202
343
  for expression in var_exprs
203
344
  ]
345
+ return _prepared(sample, _expressions(var_exprs, cut, weight_expr), requests)
204
346
 
205
347
 
206
- def _read_for(
207
- sample: Sample,
208
- var_exprs: Sequence[str],
209
- cut: Cut | None,
210
- weight_expr: str | None,
211
- *,
212
- cache: ReadCache | None = None,
213
- ) -> tuple[dict[str, Any], int]:
214
- """Read the branches needed by the variables, the selection and the weight."""
348
+ def _expressions(
349
+ var_exprs: Sequence[str], cut: Cut | None, weight_expr: str | None
350
+ ) -> list[Expression]:
351
+ """Return the parsed variables, selection and weight, whose branches are read."""
215
352
  expressions = [parse(v) for v in var_exprs]
216
353
  if cut is not None:
217
354
  expressions.append(cut.parsed())
218
355
  if weight_expr is not None:
219
356
  expressions.append(parse(weight_expr))
220
- return read_arrays(sample, expressions, cache=cache)
357
+ return expressions
221
358
 
222
359
 
223
360
  def build_histograms(
@@ -343,28 +480,46 @@ def _load_with_variations(
343
480
  ) -> _Loaded:
344
481
  """Prepare the nominal columns and every variation, reading the sample's branches once.
345
482
 
346
- Weight and branch variations are evaluated on the arrays read for the
347
- nominal; variations from other data read those. A variation is a
348
- :class:`Columns` to fill, a factor for the nominal histogram, or ``None``
349
- for a down direction mirrored from the up one.
483
+ Weight and branch variations are prepared from the chunks read for the
484
+ nominal (:func:`read_chunks`), chunk by chunk alongside it; variations from
485
+ other data read those. A variation is a :class:`Columns` to fill, a factor
486
+ for the nominal histogram, or ``None`` for a down direction mirrored from the
487
+ up one.
350
488
  """
351
489
  cut = combined_selection(sample, selection)
352
490
  cut_text = None if cut is None else cut.expression
353
491
  weight_expr = combined_weight(sample, weight)
354
492
  scale = sample.scale * sample.lumi_scale(lumi)
355
493
  expressions, used = _read_plan(sample, var, systematics, selection=selection, weight=weight)
356
- arrays, n_events = read_arrays(sample, expressions, cache=cache)
357
494
 
358
- nominal = prepare(
359
- arrays,
360
- [var.expression],
361
- selection=cut_text,
362
- weight=weight_expr,
363
- scale=scale,
364
- nonfinite=nonfinite,
365
- context=sample.label,
366
- n_events=n_events,
367
- )
495
+ def request(context: str, weight: str | None, **extra: Any) -> Request:
496
+ return Request(
497
+ (var.expression,),
498
+ selection=cut_text,
499
+ weight=weight,
500
+ scale=scale,
501
+ nonfinite=nonfinite,
502
+ context=context,
503
+ **extra,
504
+ )
505
+
506
+ requests = [request(sample.label, weight_expr)]
507
+ shifted: dict[tuple[str, str], int] = {} # the request of each weight or branch variation
508
+ for name, syst in systematics.items():
509
+ for direction, spec in (("up", syst.up), ("down", syst.down)):
510
+ if spec is None or syst.kind not in ("weight", "replace"):
511
+ continue
512
+ context = f"{sample.label} [{name} {direction}]"
513
+ shifted[name, direction] = len(requests)
514
+ note = _variation_note(context)
515
+ if syst.kind == "weight":
516
+ requests.append(request(context, _product(spec, weight), note=note))
517
+ else:
518
+ # the replacing branches take the place of the replaced ones, so the
519
+ # variable, selection and weight all see the shifted values
520
+ replaced = {old: new for old, new in spec.items() if old in used}
521
+ requests.append(request(context, weight_expr, substitutes=replaced, note=note))
522
+ prepared = _prepared(sample, expressions, requests, cache=cache)
368
523
 
369
524
  variations: dict[str, tuple[Columns | float | None, Columns | float | None]] = {}
370
525
  for name, syst in systematics.items():
@@ -390,27 +545,9 @@ def _load_with_variations(
390
545
  )
391
546
  )
392
547
  else:
393
- if syst.kind == "weight":
394
- varied_arrays, varied_weight = arrays, _product(spec, weight)
395
- else:
396
- # the replacing branches take the place of the replaced ones, so the
397
- # variable, selection and weight all see the shifted values
398
- replaced = {old: arrays[new] for old, new in spec.items() if old in used}
399
- varied_arrays, varied_weight = {**arrays, **replaced}, weight_expr
400
- shifts.append(
401
- prepare(
402
- varied_arrays,
403
- [var.expression],
404
- selection=cut_text,
405
- weight=varied_weight,
406
- scale=scale,
407
- nonfinite=nonfinite,
408
- context=context,
409
- n_events=n_events,
410
- )
411
- )
548
+ shifts.append(prepared[shifted[name, direction]])
412
549
  variations[name] = (shifts[0], shifts[1])
413
- return _Loaded(nominal, variations)
550
+ return _Loaded(prepared[0], variations)
414
551
 
415
552
 
416
553
  def _read_plan(
@@ -472,7 +609,12 @@ def _read_plan(
472
609
 
473
610
  def _in_variation(context: str) -> AbstractContextManager[None]:
474
611
  """Name the variation (sample, source, direction) in any rootfig error raised inside."""
475
- return annotate(f"while evaluating the systematic variation {context}")
612
+ return annotate(_variation_note(context))
613
+
614
+
615
+ def _variation_note(context: str) -> str:
616
+ """Return the note naming the variation ``context`` (sample, source, direction)."""
617
+ return f"while evaluating the systematic variation {context}"
476
618
 
477
619
 
478
620
  def _variant_sample(
@@ -82,7 +82,7 @@ def _fmt(value: float, precision: int) -> str:
82
82
  def summarize(columns: Columns, index: int = 0) -> Summary:
83
83
  """Compute :class:`Summary` statistics for column ``index`` of ``columns``."""
84
84
  values = columns.arrays[index]
85
- weights = columns.effective_weights()
85
+ weights = columns.weights
86
86
  n = int(values.size)
87
87
  common: dict[str, Any] = {
88
88
  "entries": n,
@@ -92,8 +92,11 @@ def summarize(columns: Columns, index: int = 0) -> Summary:
92
92
  "n_nonfinite": columns.n_nonfinite,
93
93
  "per_object": columns.per_object,
94
94
  }
95
- sum_w = float(weights.sum())
96
- sum_w2 = float((weights**2).sum())
95
+ if weights is None: # unit weights sum to the entry count
96
+ sum_w = sum_w2 = float(n)
97
+ else:
98
+ sum_w = float(weights.sum())
99
+ sum_w2 = float(np.square(weights).sum())
97
100
  if n == 0 or sum_w == 0:
98
101
  nan = float("nan")
99
102
  return Summary(
@@ -107,14 +110,22 @@ def summarize(columns: Columns, index: int = 0) -> Summary:
107
110
  _sum_w2=sum_w2,
108
111
  **common,
109
112
  )
110
- mean = float(np.average(values, weights=weights))
113
+
114
+ def average(array: np.ndarray) -> float:
115
+ # np.average, without summing the weights again (or a column of ones) every time
116
+ total = array.sum() if weights is None else np.multiply(array, weights).sum()
117
+ return float(total / sum_w)
118
+
119
+ mean = average(values)
111
120
  centred = values - mean
112
- variance = float(np.average(centred**2, weights=weights))
121
+ power = np.square(centred)
122
+ variance = average(power)
113
123
  # negative weights can make the weighted second moment negative: undefined, not an error
114
124
  std = math.sqrt(variance) if variance >= 0 else float("nan")
115
125
  n_eff = sum_w**2 / sum_w2 if sum_w2 else 0.0
116
126
  sem = std / math.sqrt(n_eff) if n_eff > 0 else float("nan")
117
- third = float(np.average(centred**3, weights=weights))
127
+ power *= centred # the cube from the square: centred**3 calls pow() for every value
128
+ third = average(power)
118
129
  skewness = third / std**3 if std > 0 else float("nan")
119
130
  return Summary(
120
131
  sum_weights=sum_w,
@@ -2,6 +2,7 @@
2
2
 
3
3
  from rootfig.io.cache import ReadCache
4
4
  from rootfig.io.sources import (
5
+ CHUNK_BYTES,
5
6
  ArraySource,
6
7
  FilesLike,
7
8
  FileSource,
@@ -11,6 +12,7 @@ from rootfig.io.sources import (
11
12
  )
12
13
 
13
14
  __all__ = [
15
+ "CHUNK_BYTES",
14
16
  "ArraySource",
15
17
  "FileSource",
16
18
  "FilesLike",