rootfig 0.6.0__tar.gz → 0.7.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 (315) hide show
  1. {rootfig-0.6.0 → rootfig-0.7.0}/CONTRIBUTING.md +10 -2
  2. {rootfig-0.6.0 → rootfig-0.7.0}/PKG-INFO +52 -5
  3. {rootfig-0.6.0 → rootfig-0.7.0}/README.md +48 -1
  4. {rootfig-0.6.0 → rootfig-0.7.0}/docs/api.md +10 -0
  5. rootfig-0.7.0/docs/batch.md +411 -0
  6. {rootfig-0.6.0 → rootfig-0.7.0}/docs/composable.md +62 -3
  7. {rootfig-0.6.0 → rootfig-0.7.0}/docs/index.md +1 -0
  8. {rootfig-0.6.0 → rootfig-0.7.0}/docs/plotting.md +15 -2
  9. {rootfig-0.6.0 → rootfig-0.7.0}/mkdocs.yml +1 -0
  10. {rootfig-0.6.0 → rootfig-0.7.0}/pyproject.toml +8 -6
  11. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/__init__.py +14 -3
  12. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/__init__.py +5 -0
  13. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/_common.py +23 -9
  14. rootfig-0.7.0/src/rootfig/api/_discover.py +527 -0
  15. rootfig-0.7.0/src/rootfig/api/_pdf.py +181 -0
  16. rootfig-0.7.0/src/rootfig/api/batch.py +992 -0
  17. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/data.py +22 -12
  18. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/plots1d.py +225 -78
  19. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/plots2d.py +2 -2
  20. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/expressions/__init__.py +10 -2
  21. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/expressions/parser.py +25 -1
  22. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/__init__.py +10 -1
  23. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/build.py +15 -3
  24. rootfig-0.7.0/src/rootfig/histograms/groups.py +50 -0
  25. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/pipeline.py +153 -59
  26. rootfig-0.7.0/src/rootfig/histograms/prefetch.py +212 -0
  27. rootfig-0.7.0/src/rootfig/histograms/sources.py +24 -0
  28. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/stored.py +76 -16
  29. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/systematics.py +3 -1
  30. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/io/__init__.py +10 -1
  31. rootfig-0.7.0/src/rootfig/io/cache.py +80 -0
  32. rootfig-0.7.0/src/rootfig/io/objects.py +165 -0
  33. rootfig-0.7.0/src/rootfig/io/schema.py +98 -0
  34. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/io/sources.py +167 -47
  35. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/__init__.py +11 -1
  36. rootfig-0.7.0/src/rootfig/model/filenames.py +68 -0
  37. rootfig-0.7.0/src/rootfig/model/groups.py +139 -0
  38. rootfig-0.7.0/src/rootfig/model/inputs.py +156 -0
  39. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/samples.py +2 -83
  40. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/variables.py +15 -21
  41. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/__init__.py +8 -0
  42. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/figure.py +84 -18
  43. rootfig-0.7.0/src/rootfig/plotting/pages.py +279 -0
  44. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/result.py +29 -1
  45. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/style.py +39 -11
  46. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_api.py +201 -1
  47. rootfig-0.7.0/tests/test_batch.py +1269 -0
  48. rootfig-0.7.0/tests/test_discovery.py +1139 -0
  49. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_expressions.py +39 -0
  50. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_histograms.py +494 -1
  51. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_io.py +448 -1
  52. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_model.py +249 -3
  53. rootfig-0.7.0/tests/test_pages.py +256 -0
  54. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_plotting.py +74 -0
  55. rootfig-0.7.0/tests/test_save_pdf.py +688 -0
  56. rootfig-0.7.0/tests/type_checks/batch.py +71 -0
  57. rootfig-0.7.0/tests/type_checks/groups.py +23 -0
  58. rootfig-0.6.0/src/rootfig/io/objects.py +0 -128
  59. {rootfig-0.6.0 → rootfig-0.7.0}/.gitignore +0 -0
  60. {rootfig-0.6.0 → rootfig-0.7.0}/LICENSE +0 -0
  61. {rootfig-0.6.0 → rootfig-0.7.0}/docs/ecosystem.md +0 -0
  62. {rootfig-0.6.0 → rootfig-0.7.0}/docs/expressions.md +0 -0
  63. {rootfig-0.6.0 → rootfig-0.7.0}/docs/gallery/index.md +0 -0
  64. {rootfig-0.6.0 → rootfig-0.7.0}/docs/hooks/gallery.py +0 -0
  65. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-alice-dark.png +0 -0
  66. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-alice.png +0 -0
  67. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-atlas-dark.png +0 -0
  68. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-atlas.png +0 -0
  69. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-cms-dark.png +0 -0
  70. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-cms.png +0 -0
  71. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-dark.png +0 -0
  72. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-dune-dark.png +0 -0
  73. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-dune.png +0 -0
  74. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-lhcb-dark.png +0 -0
  75. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays-lhcb.png +0 -0
  76. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/arrays.png +0 -0
  77. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-alice-dark.png +0 -0
  78. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-alice.png +0 -0
  79. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-atlas-dark.png +0 -0
  80. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-atlas.png +0 -0
  81. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-cms-dark.png +0 -0
  82. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-cms.png +0 -0
  83. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-dark.png +0 -0
  84. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-dune-dark.png +0 -0
  85. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-dune.png +0 -0
  86. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-lhcb-dark.png +0 -0
  87. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation-lhcb.png +0 -0
  88. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/correlation.png +0 -0
  89. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-alice-dark.png +0 -0
  90. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-alice.png +0 -0
  91. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-atlas-dark.png +0 -0
  92. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-atlas.png +0 -0
  93. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-cms-dark.png +0 -0
  94. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-cms.png +0 -0
  95. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-dark.png +0 -0
  96. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-dune-dark.png +0 -0
  97. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-dune.png +0 -0
  98. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-lhcb-dark.png +0 -0
  99. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow-lhcb.png +0 -0
  100. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/density_flow.png +0 -0
  101. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-alice-dark.png +0 -0
  102. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-alice.png +0 -0
  103. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-atlas-dark.png +0 -0
  104. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-atlas.png +0 -0
  105. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-cms-dark.png +0 -0
  106. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-cms.png +0 -0
  107. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-dark.png +0 -0
  108. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-dune-dark.png +0 -0
  109. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-dune.png +0 -0
  110. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-lhcb-dark.png +0 -0
  111. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency-lhcb.png +0 -0
  112. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/efficiency.png +0 -0
  113. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-alice-dark.png +0 -0
  114. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-alice.png +0 -0
  115. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-atlas-dark.png +0 -0
  116. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-atlas.png +0 -0
  117. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-cms-dark.png +0 -0
  118. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-cms.png +0 -0
  119. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-dark.png +0 -0
  120. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-dune-dark.png +0 -0
  121. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-dune.png +0 -0
  122. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-lhcb-dark.png +0 -0
  123. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions-lhcb.png +0 -0
  124. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/expressions.png +0 -0
  125. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-alice-dark.png +0 -0
  126. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-alice.png +0 -0
  127. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-atlas-dark.png +0 -0
  128. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-atlas.png +0 -0
  129. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-cms-dark.png +0 -0
  130. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-cms.png +0 -0
  131. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-dark.png +0 -0
  132. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-dune-dark.png +0 -0
  133. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-dune.png +0 -0
  134. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-lhcb-dark.png +0 -0
  135. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats-lhcb.png +0 -0
  136. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/fill_stats.png +0 -0
  137. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-alice-dark.png +0 -0
  138. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-alice.png +0 -0
  139. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-atlas-dark.png +0 -0
  140. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-atlas.png +0 -0
  141. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-cms-dark.png +0 -0
  142. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-cms.png +0 -0
  143. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-dark.png +0 -0
  144. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-dune-dark.png +0 -0
  145. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-dune.png +0 -0
  146. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-lhcb-dark.png +0 -0
  147. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d-lhcb.png +0 -0
  148. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/hist2d.png +0 -0
  149. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-alice-dark.png +0 -0
  150. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-alice.png +0 -0
  151. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-atlas-dark.png +0 -0
  152. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-atlas.png +0 -0
  153. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-cms-dark.png +0 -0
  154. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-cms.png +0 -0
  155. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-dark.png +0 -0
  156. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-dune-dark.png +0 -0
  157. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-dune.png +0 -0
  158. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-lhcb-dark.png +0 -0
  159. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes-lhcb.png +0 -0
  160. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/log_axes.png +0 -0
  161. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/luminosity-dark.png +0 -0
  162. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/luminosity.png +0 -0
  163. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/many_plots-dark.png +0 -0
  164. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/many_plots.png +0 -0
  165. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-alice-dark.png +0 -0
  166. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-alice.png +0 -0
  167. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-atlas-dark.png +0 -0
  168. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-atlas.png +0 -0
  169. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-cms-dark.png +0 -0
  170. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-cms.png +0 -0
  171. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-dark.png +0 -0
  172. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-dune-dark.png +0 -0
  173. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-dune.png +0 -0
  174. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-lhcb-dark.png +0 -0
  175. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event-lhcb.png +0 -0
  176. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/object_vs_event.png +0 -0
  177. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-alice-dark.png +0 -0
  178. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-alice.png +0 -0
  179. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-atlas-dark.png +0 -0
  180. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-atlas.png +0 -0
  181. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-cms-dark.png +0 -0
  182. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-cms.png +0 -0
  183. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-dark.png +0 -0
  184. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-dune-dark.png +0 -0
  185. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-dune.png +0 -0
  186. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-lhcb-dark.png +0 -0
  187. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio-lhcb.png +0 -0
  188. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/overlay_ratio.png +0 -0
  189. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-alice-dark.png +0 -0
  190. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-alice.png +0 -0
  191. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-atlas-dark.png +0 -0
  192. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-atlas.png +0 -0
  193. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-cms-dark.png +0 -0
  194. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-cms.png +0 -0
  195. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-dark.png +0 -0
  196. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-dune-dark.png +0 -0
  197. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-dune.png +0 -0
  198. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-lhcb-dark.png +0 -0
  199. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile-lhcb.png +0 -0
  200. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/profile.png +0 -0
  201. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-alice-dark.png +0 -0
  202. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-alice.png +0 -0
  203. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-atlas-dark.png +0 -0
  204. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-atlas.png +0 -0
  205. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-cms-dark.png +0 -0
  206. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-cms.png +0 -0
  207. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-dark.png +0 -0
  208. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-dune-dark.png +0 -0
  209. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-dune.png +0 -0
  210. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-lhcb-dark.png +0 -0
  211. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick-lhcb.png +0 -0
  212. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/quick.png +0 -0
  213. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-alice-dark.png +0 -0
  214. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-alice.png +0 -0
  215. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-atlas-dark.png +0 -0
  216. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-atlas.png +0 -0
  217. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-cms-dark.png +0 -0
  218. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-cms.png +0 -0
  219. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-dark.png +0 -0
  220. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-dune-dark.png +0 -0
  221. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-dune.png +0 -0
  222. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-lhcb-dark.png +0 -0
  223. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference-lhcb.png +0 -0
  224. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/ratio_reference.png +0 -0
  225. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/robust_range-dark.png +0 -0
  226. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/robust_range.png +0 -0
  227. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-alice-dark.png +0 -0
  228. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-alice.png +0 -0
  229. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-atlas-dark.png +0 -0
  230. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-atlas.png +0 -0
  231. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-cms-dark.png +0 -0
  232. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-cms.png +0 -0
  233. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-dark.png +0 -0
  234. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-dune-dark.png +0 -0
  235. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-dune.png +0 -0
  236. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-lhcb-dark.png +0 -0
  237. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data-lhcb.png +0 -0
  238. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/stack_data.png +0 -0
  239. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/style_colors-dark.png +0 -0
  240. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/style_colors.png +0 -0
  241. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-alice-dark.png +0 -0
  242. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-alice.png +0 -0
  243. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-atlas-dark.png +0 -0
  244. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-atlas.png +0 -0
  245. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-cms-dark.png +0 -0
  246. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-cms.png +0 -0
  247. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-dark.png +0 -0
  248. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-dune-dark.png +0 -0
  249. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-dune.png +0 -0
  250. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-lhcb-dark.png +0 -0
  251. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics-lhcb.png +0 -0
  252. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/systematics.png +0 -0
  253. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-alice-dark.png +0 -0
  254. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-alice.png +0 -0
  255. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-atlas-dark.png +0 -0
  256. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-atlas.png +0 -0
  257. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-cms-dark.png +0 -0
  258. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-cms.png +0 -0
  259. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-dark.png +0 -0
  260. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-dune-dark.png +0 -0
  261. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-dune.png +0 -0
  262. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-lhcb-dark.png +0 -0
  263. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins-lhcb.png +0 -0
  264. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/variable_bins.png +0 -0
  265. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-alice-dark.png +0 -0
  266. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-alice.png +0 -0
  267. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-atlas-dark.png +0 -0
  268. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-atlas.png +0 -0
  269. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-cms-dark.png +0 -0
  270. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-cms.png +0 -0
  271. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-dark.png +0 -0
  272. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-dune-dark.png +0 -0
  273. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-dune.png +0 -0
  274. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-lhcb-dark.png +0 -0
  275. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio-lhcb.png +0 -0
  276. {rootfig-0.6.0 → rootfig-0.7.0}/docs/images/gallery/xbreak_ratio.png +0 -0
  277. {rootfig-0.6.0 → rootfig-0.7.0}/docs/quickstart.md +0 -0
  278. {rootfig-0.6.0 → rootfig-0.7.0}/docs/stylesheets/gallery.css +0 -0
  279. {rootfig-0.6.0 → rootfig-0.7.0}/examples/gallery/__init__.py +0 -0
  280. {rootfig-0.6.0 → rootfig-0.7.0}/examples/gallery/__main__.py +0 -0
  281. {rootfig-0.6.0 → rootfig-0.7.0}/examples/gallery/data.py +0 -0
  282. {rootfig-0.6.0 → rootfig-0.7.0}/examples/gallery/registry.py +0 -0
  283. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/_mapping.py +0 -0
  284. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/_storage.py +0 -0
  285. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/_typing.py +0 -0
  286. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/_hists.py +0 -0
  287. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/measures.py +0 -0
  288. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/api/tables.py +0 -0
  289. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/errors.py +0 -0
  290. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/expressions/functions.py +0 -0
  291. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/cutflow.py +0 -0
  292. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/efficiency.py +0 -0
  293. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/normalize.py +0 -0
  294. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/ratio.py +0 -0
  295. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/histograms/stats.py +0 -0
  296. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/binning.py +0 -0
  297. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/cuts.py +0 -0
  298. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/style.py +0 -0
  299. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/systematics.py +0 -0
  300. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/model/units.py +0 -0
  301. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/annotations.py +0 -0
  302. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/correlation.py +0 -0
  303. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/hist1d.py +0 -0
  304. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/hist2d.py +0 -0
  305. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/points.py +0 -0
  306. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/plotting/ratio.py +0 -0
  307. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/py.typed +0 -0
  308. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/selection/__init__.py +0 -0
  309. {rootfig-0.6.0 → rootfig-0.7.0}/src/rootfig/selection/columns.py +0 -0
  310. {rootfig-0.6.0 → rootfig-0.7.0}/tests/conftest.py +0 -0
  311. {rootfig-0.6.0 → rootfig-0.7.0}/tests/data/split_collection.root +0 -0
  312. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_gallery.py +0 -0
  313. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_selection.py +0 -0
  314. {rootfig-0.6.0 → rootfig-0.7.0}/tests/test_tutorials.py +0 -0
  315. {rootfig-0.6.0 → rootfig-0.7.0}/tests/type_checks/systematics.py +0 -0
@@ -32,6 +32,14 @@ that use ROOT's tutorial files run only when `root-config` is available
32
32
  locally and are skipped otherwise; do not add
33
33
  tests that require ROOT or network access.
34
34
 
35
+ `key4hep.yml` runs the tests on the Key4hep nightlies (LCG `devkey-head`) and the
36
+ latest Key4hep release, weekly as well, since the stacks change without a commit
37
+ here. `.github/scripts/key4hep-test.sh` installs rootfig into a
38
+ [cvmfs-venv](https://github.com/jbeirer/cvmfs-venv) with `--no-index`, so every
39
+ runtime dependency must come from the stack: the floors in `pyproject.toml` stay
40
+ at or below what the nightlies ship. Locally: `.github/scripts/key4hep-test.sh
41
+ /cvmfs/sw-nightlies.hsf.org/key4hep/setup.sh`.
42
+
35
43
  ## Layout
36
44
 
37
45
  ```
@@ -40,9 +48,9 @@ src/rootfig/
40
48
  errors.py exception hierarchy
41
49
  expressions/ parse, validate and evaluate expression strings
42
50
  io/ file and in-memory data sources
43
- model/ Sample, Variable, Cut, Style, binning
51
+ model/ Sample, Group, Variable, Cut, Style, binning
44
52
  selection/ per-event / per-object semantics -> flat columns
45
- histograms/ filling, normalisation, ratios, statistics, pipeline
53
+ histograms/ filling, normalisation, ratios, statistics, groups, pipeline
46
54
  plotting/ matplotlib/mplhep rendering, styles, annotations
47
55
  tests/ one module per layer plus end-to-end API tests
48
56
  docs/ MkDocs sources
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rootfig
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Publication-quality figures straight from ROOT trees, 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/
@@ -24,11 +24,11 @@ Classifier: Topic :: Scientific/Engineering :: Visualization
24
24
  Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.12
26
26
  Requires-Dist: awkward>=2.8
27
- Requires-Dist: hist>=2.7.2
27
+ Requires-Dist: hist>=2.9
28
28
  Requires-Dist: matplotlib>=3.11
29
- Requires-Dist: mplhep>=1.3
29
+ Requires-Dist: mplhep>=1.2.1
30
30
  Requires-Dist: numpy>=1.26
31
- Requires-Dist: uproot>=5.7.4
31
+ Requires-Dist: uproot>=5.7.1
32
32
  Description-Content-Type: text/markdown
33
33
 
34
34
  <p align="center">
@@ -53,6 +53,7 @@ Description-Content-Type: text/markdown
53
53
  <a href="https://jbeirer.github.io/rootfig/"><img src="https://img.shields.io/badge/docs-online-blue" alt="Documentation"></a>
54
54
  <a href="https://doi.org/10.5281/zenodo.22726311"><img src="https://zenodo.org/badge/1366702602.svg" alt="DOI"></a>
55
55
  <a href="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
56
+ <a href="https://github.com/jbeirer/rootfig/actions/workflows/key4hep.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/key4hep.yml/badge.svg" alt="Key4hep"></a>
56
57
  <a href="https://codecov.io/gh/jbeirer/rootfig"><img src="https://codecov.io/gh/jbeirer/rootfig/branch/main/graph/badge.svg" alt="codecov"></a>
57
58
  <a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/v/rootfig.svg" alt="PyPI"></a>
58
59
  <a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/pyversions/rootfig.svg" alt="Python"></a>
@@ -150,6 +151,45 @@ Everything you get back is a standard object: `p.fig` and `p.ax` are
150
151
  matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
151
152
  `rf.load(...)` returns Awkward arrays.
152
153
 
154
+ Samples that belong to one physics category are drawn as one histogram with
155
+ `rf.Group`. Each keeps its own files, weights, cross section and systematics;
156
+ they are summed only after filling:
157
+
158
+ ```python
159
+ ww = rf.Sample("ww.root", tree="events", label="WW", weight="mc_weight")
160
+ zz = rf.Sample("zz.root", tree="events", label="ZZ", weight="mc_weight")
161
+ vv = rf.Group([ww, zz], label="VV")
162
+
163
+ rf.plot([vv, signal], pt, observed=data, stack=True, ratio=True, style=style)
164
+ ```
165
+
166
+ Whole sets of plots, every variable under each selection in every drawing
167
+ variant, are one `rf.PlotBook`: it runs that same `rf.plot` call per
168
+ combination and writes deterministically named files:
169
+
170
+ ```python
171
+ book = rf.PlotBook(
172
+ [vv, signal],
173
+ [pt, rf.Variable("MET", bins=(40, 0, 200), unit="GeV")],
174
+ selections={"baseline": baseline, "sr": baseline & "MET > 50"},
175
+ variants={"lin": {}, "log": {"logy": True}},
176
+ plot_kwargs={"observed": data, "stack": True, "ratio": True, "style": style},
177
+ )
178
+ book.save("plots/", formats=["pdf", "png"]) # plots/Muon_pt__sr__log.pdf, ...
179
+ book.save_pdf("overview.pdf") # one automatically arranged multipage PDF
180
+ ```
181
+
182
+ `rf.ALL` discovers the variables instead, from the branch types and stored
183
+ histograms of the files (metadata only), filtered by name:
184
+
185
+ ```python
186
+ book = rf.PlotBook(
187
+ [vv, signal],
188
+ variables=rf.ALL,
189
+ exclude=["*_cov", "*Index"],
190
+ )
191
+ ```
192
+
153
193
  ## What you can do
154
194
 
155
195
  - **Select events and objects with readable expressions.** Write cuts such as
@@ -159,13 +199,20 @@ matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
159
199
  ratio panels share binning and propagate histogram uncertainties; bin edges
160
200
  and `(n, low, high)` are used as given, while a range inferred from the data
161
201
  ignores far outliers, so `-999` sentinels do not set the axis. Normalise to
162
- unity, density, bin width or luminosity.
202
+ unity, density, bin width or luminosity. Draw several samples as one
203
+ histogram with `rf.Group`, each keeping its own weights, cross section and
204
+ systematics.
163
205
  - **Show systematic uncertainties.** Attach weight, branch, file or
164
206
  normalisation variations to a sample; stacks and ratio panels draw the
165
207
  combined statistical and systematic band, and every component stays
166
208
  accessible.
167
209
  - **Style figures for your analysis.** Add experiment labels, units, log axes
168
210
  and broken axes, then refine the result with matplotlib.
211
+ - **Produce whole sets of plots.** `rf.PlotBook` runs one `rf.plot` call over
212
+ variables × selections × variants, lazily, and saves each under a
213
+ deterministic name or all of them as one multipage PDF; `rf.ALL` discovers
214
+ the variables from the files, and `select()` filters the book down while
215
+ iterating on a plot.
169
216
  - **Go beyond 1D plots.** Draw 2D histograms, correlations, efficiencies,
170
217
  profiles, resolutions and significance panels; produce cut flows and
171
218
  summary statistics from the same inputs.
@@ -20,6 +20,7 @@
20
20
  <a href="https://jbeirer.github.io/rootfig/"><img src="https://img.shields.io/badge/docs-online-blue" alt="Documentation"></a>
21
21
  <a href="https://doi.org/10.5281/zenodo.22726311"><img src="https://zenodo.org/badge/1366702602.svg" alt="DOI"></a>
22
22
  <a href="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
23
+ <a href="https://github.com/jbeirer/rootfig/actions/workflows/key4hep.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/key4hep.yml/badge.svg" alt="Key4hep"></a>
23
24
  <a href="https://codecov.io/gh/jbeirer/rootfig"><img src="https://codecov.io/gh/jbeirer/rootfig/branch/main/graph/badge.svg" alt="codecov"></a>
24
25
  <a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/v/rootfig.svg" alt="PyPI"></a>
25
26
  <a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/pyversions/rootfig.svg" alt="Python"></a>
@@ -117,6 +118,45 @@ Everything you get back is a standard object: `p.fig` and `p.ax` are
117
118
  matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
118
119
  `rf.load(...)` returns Awkward arrays.
119
120
 
121
+ Samples that belong to one physics category are drawn as one histogram with
122
+ `rf.Group`. Each keeps its own files, weights, cross section and systematics;
123
+ they are summed only after filling:
124
+
125
+ ```python
126
+ ww = rf.Sample("ww.root", tree="events", label="WW", weight="mc_weight")
127
+ zz = rf.Sample("zz.root", tree="events", label="ZZ", weight="mc_weight")
128
+ vv = rf.Group([ww, zz], label="VV")
129
+
130
+ rf.plot([vv, signal], pt, observed=data, stack=True, ratio=True, style=style)
131
+ ```
132
+
133
+ Whole sets of plots, every variable under each selection in every drawing
134
+ variant, are one `rf.PlotBook`: it runs that same `rf.plot` call per
135
+ combination and writes deterministically named files:
136
+
137
+ ```python
138
+ book = rf.PlotBook(
139
+ [vv, signal],
140
+ [pt, rf.Variable("MET", bins=(40, 0, 200), unit="GeV")],
141
+ selections={"baseline": baseline, "sr": baseline & "MET > 50"},
142
+ variants={"lin": {}, "log": {"logy": True}},
143
+ plot_kwargs={"observed": data, "stack": True, "ratio": True, "style": style},
144
+ )
145
+ book.save("plots/", formats=["pdf", "png"]) # plots/Muon_pt__sr__log.pdf, ...
146
+ book.save_pdf("overview.pdf") # one automatically arranged multipage PDF
147
+ ```
148
+
149
+ `rf.ALL` discovers the variables instead, from the branch types and stored
150
+ histograms of the files (metadata only), filtered by name:
151
+
152
+ ```python
153
+ book = rf.PlotBook(
154
+ [vv, signal],
155
+ variables=rf.ALL,
156
+ exclude=["*_cov", "*Index"],
157
+ )
158
+ ```
159
+
120
160
  ## What you can do
121
161
 
122
162
  - **Select events and objects with readable expressions.** Write cuts such as
@@ -126,13 +166,20 @@ matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
126
166
  ratio panels share binning and propagate histogram uncertainties; bin edges
127
167
  and `(n, low, high)` are used as given, while a range inferred from the data
128
168
  ignores far outliers, so `-999` sentinels do not set the axis. Normalise to
129
- unity, density, bin width or luminosity.
169
+ unity, density, bin width or luminosity. Draw several samples as one
170
+ histogram with `rf.Group`, each keeping its own weights, cross section and
171
+ systematics.
130
172
  - **Show systematic uncertainties.** Attach weight, branch, file or
131
173
  normalisation variations to a sample; stacks and ratio panels draw the
132
174
  combined statistical and systematic band, and every component stays
133
175
  accessible.
134
176
  - **Style figures for your analysis.** Add experiment labels, units, log axes
135
177
  and broken axes, then refine the result with matplotlib.
178
+ - **Produce whole sets of plots.** `rf.PlotBook` runs one `rf.plot` call over
179
+ variables × selections × variants, lazily, and saves each under a
180
+ deterministic name or all of them as one multipage PDF; `rf.ALL` discovers
181
+ the variables from the files, and `select()` filters the book down while
182
+ iterating on a plot.
136
183
  - **Go beyond 1D plots.** Draw 2D histograms, correlations, efficiencies,
137
184
  profiles, resolutions and significance panels; produce cut flows and
138
185
  summary statistics from the same inputs.
@@ -22,11 +22,21 @@
22
22
  ## Descriptions
23
23
 
24
24
  ::: rootfig.Sample
25
+ ::: rootfig.Group
25
26
  ::: rootfig.Variable
26
27
  ::: rootfig.Cut
27
28
  ::: rootfig.Style
28
29
  ::: rootfig.Systematic
29
30
 
31
+ ## Batch plotting
32
+
33
+ ::: rootfig.PlotBook
34
+ ::: rootfig.PlotTask
35
+ ::: rootfig.ALL
36
+ ::: rootfig.discover_variables
37
+ ::: rootfig.model.check_file_stem
38
+ ::: rootfig.model.safe_file_stem
39
+
30
40
  ## Results
31
41
 
32
42
  ::: rootfig.Plot
@@ -0,0 +1,411 @@
1
+ # Batch plotting
2
+
3
+ An analysis rarely needs one plot. It needs every variable, under each
4
+ selection, drawn linear and logarithmic. `rf.PlotBook` describes that set once
5
+ and runs [`rf.plot`][rootfig.plot] for each member: it is repeated
6
+ `rf.plot(...)`, not a second plotting engine.
7
+
8
+ ```python
9
+ import rootfig as rf
10
+
11
+ ww = rf.Sample("ww.root", tree="events", label="WW", weight="mc_weight")
12
+ zz = rf.Sample("zz.root", tree="events", label="ZZ", weight="mc_weight")
13
+ signal = rf.Sample("signal.root", tree="events", label="Signal", weight="mc_weight")
14
+
15
+ book = rf.PlotBook(
16
+ [rf.Group([ww, zz], label="VV"), signal],
17
+ [
18
+ rf.Variable("mass", bins=(50, 100, 150), unit="GeV"),
19
+ rf.Variable("pt", bins=(50, 0, 200), unit="GeV"),
20
+ ],
21
+ selections={
22
+ "baseline": rf.Cut("nMuon >= 2"),
23
+ "sr": rf.Cut("nMuon >= 2") & "recoil_mass > 120",
24
+ },
25
+ variants={
26
+ "lin": {},
27
+ "log": {"logy": True},
28
+ },
29
+ plot_kwargs={"stack": True, "style": rf.Style(experiment="ATLAS", status="Internal")},
30
+ )
31
+
32
+ book.save("plots", formats=["pdf", "png"])
33
+ ```
34
+
35
+ This writes eight plots in two formats, `plots/mass__baseline__lin.pdf`,
36
+ `plots/mass__baseline__lin.png`, ... `plots/pt__sr__log.png`.
37
+
38
+ ## The three axes
39
+
40
+ The tasks of a book are the Cartesian product
41
+
42
+ ```text
43
+ variables × selections × variants
44
+ ```
45
+
46
+ in insertion order, variables outermost and variants innermost. Two variables,
47
+ three selections and two variants give twelve tasks, and `book.tasks()` lists
48
+ them without reading or drawing anything.
49
+
50
+ **Variables** are names, expressions or [`Variable`](composable.md#variable)
51
+ objects; one may be given bare (`rf.PlotBook(data, "mass")`).
52
+ `Variable.safe_name` (the `name=`, else
53
+ [`safe_file_stem`][rootfig.model.safe_file_stem] of the expression) identifies a
54
+ variable and names its files, so
55
+ two variables of one book must not share it: `Variable("Muon_pt", name="pt")`
56
+ next to `Variable("Electron_pt", name="pt")` is rejected.
57
+
58
+ **Selections** are a mapping from a name to a cut (a string, a
59
+ [`Cut`](composable.md#cut) or `None` for "no cut"). Names are the file name
60
+ component, never derived from the expression or the cut's label.
61
+
62
+ **Variants** are a mapping from a name to `plot()` keywords. A variant's
63
+ keywords override the common `plot_kwargs` for its tasks, so
64
+
65
+ ```python
66
+ book = rf.PlotBook(
67
+ data,
68
+ ["mass"],
69
+ plot_kwargs={"stack": True, "logy": False},
70
+ variants={"lin": {}, "log": {"logy": True}},
71
+ )
72
+ ```
73
+
74
+ draws `lin` with `stack=True, logy=False` and `log` with `stack=True, logy=True`.
75
+
76
+ Every other keyword of `rf.plot` (`observed=`, `normalize=`, `ratio=`,
77
+ `systematics=`, `style=`, ...) goes into `plot_kwargs` or a variant. The keyword
78
+ names are checked against `rf.plot`'s signature when the book is built, so a
79
+ misspelt `log_y` is reported, with `logy` as the suggestion, before anything is
80
+ drawn rather than after the first tasks have written their files; the values are
81
+ validated by `rf.plot` itself. Five keywords are the book's own and are rejected:
82
+ `data`, `variable` and `selection` come from the task, `save` from
83
+ `PlotBook.save()`, and `ax` because every task draws its own figure.
84
+
85
+ A reserved keyword, an empty `selections=` or `variants=`, an unusable name and a
86
+ pair of tasks whose files would collide all raise `ValueError` when the book is
87
+ built. A `selections=`, `variants=` or `plot_kwargs=` that is not a mapping, a
88
+ name that is not a string, and a keyword `rf.plot` does not take, raise
89
+ `TypeError`.
90
+
91
+ Selection and variant names become file name components and are checked, like
92
+ `Variable.name`, for every platform: more than dots and spaces, no trailing dot
93
+ or space, no slash, backslash, control character or `<>:"|?*`, and not a Windows
94
+ device name (`CON`, `NUL`, `COM1`, ...). The check is
95
+ [`check_file_stem`][rootfig.model.check_file_stem]; its message offers a
96
+ spelling that works.
97
+
98
+ ## Automatic variable discovery
99
+
100
+ `rf.ALL` in place of the variable list asks the book to find the variables
101
+ itself:
102
+
103
+ ```python
104
+ book = rf.PlotBook(
105
+ samples,
106
+ variables=rf.ALL,
107
+ )
108
+ ```
109
+
110
+ Discovery reads metadata only. For a `TTree` or `RNTuple` it is the schema:
111
+ every branch (or nested field, `Muon.pt`) whose values are numbers or booleans,
112
+ lists and fixed-size arrays of them included, becomes a variable; strings,
113
+ records and other objects are left out. For a ROOT file read without a tree it
114
+ is the object list: every stored `TH1` becomes a variable; `TH2`, `TProfile`,
115
+ `TParameter` and the like do not, and a histogram inside a directory is named by
116
+ its path (`sel/mz`). A file holding both is treated as `rf.plot` treats it: the
117
+ branches of its one tree plus the stored `TH1`s no branch shadows; an explicit
118
+ `tree=` means branches only, and several trees without `tree=` raise, as they
119
+ do for any plot. In-memory arrays contribute their numeric and boolean columns.
120
+ No event array and no bin content is read, and the first file of a sample
121
+ stands for all of them, as everywhere in rootfig.
122
+
123
+ With several samples, groups (through their leaf samples) and `observed=`, a
124
+ variable must be present in every one of them, the same way: a name that is a
125
+ branch in one sample and a stored histogram in another is not a common
126
+ variable. Variants that change how the histograms are prepared (`tree`,
127
+ `observed`, `weight`, ...) must all be able to plot it too, so the discovered
128
+ set is the intersection over the effective configurations. The files or arrays
129
+ a `Systematic.samples` variation fills or reads from are surveyed like a sample
130
+ of their own and take part in the intersection, in the mode of the sample they
131
+ vary; a variation that cannot be built or surveyed is left to the task, which
132
+ reports it whatever the variable. A branch that a branch-replacement systematic
133
+ replaces is kept only when its replacement is a plottable branch of the sample,
134
+ since the replacement is read whenever the branch is. Stored histograms
135
+ are left out whenever the book is bound to refuse them: a selection, a
136
+ `weight`, `nonfinite="error"`, a `stats` box, a `range` without `bins`, or a
137
+ systematic varying the weight or branches that applies to a sample (the plot's
138
+ unless the sample's own source of that name replaces it, none for observed
139
+ data), in the book's keywords or in any variant's. A file of stored histograms
140
+ only then fails when the book is built, saying why, rather than at its first
141
+ task; the message lists what each sample holds, what was left out and why, and
142
+ which names the samples do not share.
143
+
144
+ `include=` and `exclude=` narrow the set with case-sensitive shell patterns
145
+ (`*`, `?`, `[...]`, as `fnmatch` reads them), one or a sequence:
146
+
147
+ ```python
148
+ book = rf.PlotBook(
149
+ samples,
150
+ variables=rf.ALL,
151
+ include=["Muon_*", "Electron_*", "MET*"],
152
+ exclude=["*_cov", "*Index"],
153
+ )
154
+ ```
155
+
156
+ A variable is kept when it matches one `include` pattern (all do when `include`
157
+ is not given) and no `exclude` pattern. Patterns match the source name, `sel/mz`
158
+ or `jet1_b-tag`, not the file name component `sel_mz` made from it. They are
159
+ only valid with `rf.ALL`: an explicit list is used as it is, and `include=` or
160
+ `exclude=` next to one raises. So does a filter that leaves nothing; a pattern
161
+ that matches nothing is fine as long as others do.
162
+
163
+ The result is an ordinary tuple of `Variable` objects, sorted by source name,
164
+ each addressing exactly its branch or histogram (`` `jet1_b-tag` `` and
165
+ `` `sel/mz` `` in backticks). `book.variables` shows exactly what was
166
+ discovered, and from there on nothing distinguishes the book from one built
167
+ with that list: the same file names (two names that sanitise to one component,
168
+ `a-b` and `a_b`, are rejected as for an explicit list; exclude one or name
169
+ them), the same `tasks()`, `select()` (which keeps the discovered variables
170
+ rather than discovering again) and the same batched execution described below.
171
+
172
+ `rf.discover_variables(data, ...)` runs the same discovery without building a
173
+ book, under the same `selections=`, `variants=`, `plot_kwargs=`, `include=` and
174
+ `exclude=` keywords, and returns the variables. It does not check output file
175
+ names, so two names that sanitise to one component are both returned; tell them
176
+ apart with `Variable.replace(name=...)` and pass the list to `PlotBook`:
177
+
178
+ ```python
179
+ variables = rf.discover_variables(samples, include="jet*")
180
+ renamed = [v.replace(name="jet1_btag") if v.expression == "`jet1_b-tag`" else v for v in variables]
181
+ book = rf.PlotBook(samples, renamed)
182
+ ```
183
+
184
+ ## Data passes through unchanged
185
+
186
+ `data` is stored as given and handed to `rf.plot` as is, never copied or
187
+ flattened; with an explicit variable list it is not inspected either, and
188
+ `rf.ALL` reads its metadata only. Every form of `data` that `rf.plot` accepts
189
+ can therefore be used in a book: file paths and globs, `Sample` objects,
190
+ `Group` objects (drawn as one histogram), a `Group` as `observed=`, variables
191
+ that name a `TH1` stored in the files, in-memory arrays and `hist.Hist` or
192
+ `Histogram` objects. The rules of `rf.plot` apply unchanged: a book always
193
+ names at least one variable (`rf.ALL` needs inputs with names to discover, so
194
+ histogram objects take an explicit one), and a `selection` or `weight` raises
195
+ for a ready-made histogram, which is drawn as it is.
196
+
197
+ The book copies the mappings it is configured with (the variable list,
198
+ `selections`, `variants` and each keyword mapping) into read-only copies, so
199
+ adding to or replacing entries of those dictionaries afterwards does not change
200
+ the book. The values inside them are shared, not copied: a `Style`, a `Group` or
201
+ a `systematics=` mapping given in `plot_kwargs` is the caller's object, and
202
+ changing it changes what the book draws.
203
+
204
+ ## File names
205
+
206
+ `book.save(directory, formats=("pdf",), **savefig_kwargs)` creates the directory
207
+ if needed and writes one file per task and format, named
208
+
209
+ ```text
210
+ <variable>[__<selection>][__<variant>].<format>
211
+ ```
212
+
213
+ A component appears whenever its axis was given explicitly, whatever the name:
214
+
215
+ | Book | File |
216
+ | --- | --- |
217
+ | `rf.PlotBook(data, ["mass"])` | `mass.pdf` |
218
+ | `rf.PlotBook(data, ["mass"], selections={"all": cut})` | `mass__all.pdf` |
219
+ | `rf.PlotBook(data, ["mass"], variants={"default": {"logy": True}})` | `mass__default.pdf` |
220
+ | both explicit | `mass__sr__log.pdf` |
221
+
222
+ One format may be given bare, `formats="png"`, a leading dot is accepted and
223
+ duplicates are dropped. A format matplotlib cannot write raises `ValueError`
224
+ before the first figure is drawn, rather than part way through the batch. The
225
+ remaining `savefig_kwargs` go to [`Plot.save`][rootfig.Plot.save]; `format` and
226
+ `fname` are refused, because the file names come from `formats` and the task.
227
+
228
+ Two tasks that would share a name are rejected when the book is built, not
229
+ when the second file overwrites the first. Names are compared ignoring case and
230
+ Unicode normalisation, since `lin` and `LIN` are one file on the case-insensitive
231
+ file systems of macOS and Windows; the message lists the spellings that clash.
232
+ `save()` returns the written paths in task order, then format order, and closes
233
+ every figure after writing it, also when writing fails, so memory stays bounded
234
+ however large the book is.
235
+
236
+ ## One multipage PDF
237
+
238
+ `book.save_pdf(path)` writes every task into a single PDF, several plots per
239
+ page, arranged automatically:
240
+
241
+ ```python
242
+ book.save_pdf("overview.pdf")
243
+ ```
244
+
245
+ The two output modes differ only in where the plots land:
246
+
247
+ ```text
248
+ book.save("plots/") -> one file per task
249
+ book.save_pdf("plots.pdf") -> one multipage PDF
250
+ ```
251
+
252
+ The plots follow `book.tasks()` order and fill each page row by row, and each
253
+ is drawn as `rf.plot` would draw it on a figure of its own: the same panels,
254
+ broken axis, style, labels and legend, inside one cell of the page. Nothing is
255
+ added to a cell; a `title=` in `plot_kwargs` or a variant appears as it would on
256
+ a standalone figure.
257
+
258
+ By default the grid of a page follows the plots it holds. Plots without a lower
259
+ panel or broken axis go up to `2 × 3` on a page; when one of the plots that
260
+ would land on a page has a `ratio=` panel or an `xbreak=`, which need the room,
261
+ the page stops at `2 × 2`. The last page adapts to what is left rather than
262
+ leaving a lone plot in the corner of a full grid: seven plain plots are a
263
+ `2 × 3` page followed by a `1 × 1` one, eight a `2 × 3` page and a `1 × 2` one.
264
+ `layout=(rows, columns)` fixes the grid of every page instead, the last one
265
+ included; its unused cells stay empty:
266
+
267
+ ```python
268
+ book.save_pdf("overview.pdf", layout=(2, 3))
269
+ ```
270
+
271
+ A page is as large as the grid of figures the plots would have on their own, so
272
+ a plot in a cell keeps its usual size: every cell of the document is as large as
273
+ the largest figure any plot of the book would draw alone (a ratio panel makes it
274
+ taller, a `Style` with a `figsize` sets its own). `figsize=` on `save_pdf()`
275
+ sets the size of the whole page instead:
276
+
277
+ ```python
278
+ book.save_pdf("overview.pdf", figsize=(16, 10))
279
+ ```
280
+
281
+ Each cell carries its own style, so plots with different fonts, sizes or colours
282
+ sit side by side. The one thing a cell cannot have of its own is the *page*
283
+ background, which belongs to the figure the whole page is drawn on: plots sharing
284
+ a page whose styles ask for different backgrounds are refused before anything is
285
+ drawn, since the labels and legend of a cell are drawn outside its axes and would
286
+ land on the wrong background. Give them pages of their own to keep one document,
287
+ or write a document per style:
288
+
289
+ ```python
290
+ book.save_pdf("overview.pdf", layout=(1, 1)) # one plot per page
291
+ for name in ("light", "dark"): # or one file per style
292
+ book.select(variants=name).save_pdf(f"overview-{name}.pdf")
293
+ ```
294
+
295
+ A `figsize` in `plot_kwargs` or a variant has no meaning inside a shared page and
296
+ is rejected before anything is read; pass it to `save_pdf()`. What a task runs
297
+ with decides: a `figsize` in `plot_kwargs` that every variant overrides with
298
+ `None` leaves no task with one and is accepted.
299
+
300
+ `.pdf` is appended to a path without a suffix (`"overview"` writes
301
+ `overview.pdf`), parent directories are created, another suffix raises
302
+ `ValueError` and an existing directory `IsADirectoryError`. The remaining
303
+ keywords go to `PdfPages.savefig` for every page (`dpi=` for rasterised parts),
304
+ except `metadata=`, the document information dictionary (`{"Title": ...}`), which
305
+ describes the whole PDF and is applied to it; `format`, `fname`, `figure` and
306
+ `backend` are refused. The document is written to a temporary file next to its
307
+ final name and renamed onto it once every page is done, so a failure part way
308
+ leaves an existing file as it was and no half-written PDF behind. Pages are
309
+ written one after another and each page's figure is closed before the next
310
+ begins, so memory stays bounded however many plots the book holds.
311
+
312
+ The histograms are prepared exactly as for `plots()` and `save()`, with the same
313
+ batched reads and the same reuse of one preparation for the variants that only
314
+ change the drawing (see below), so `rf.ALL` composes naturally:
315
+
316
+ ```python
317
+ rf.PlotBook(
318
+ "analysis.root",
319
+ rf.ALL,
320
+ exclude=["*_cov", "*Index"],
321
+ ).save_pdf("overview.pdf")
322
+ ```
323
+
324
+ writes an overview of every plottable branch of the file, and
325
+ `book.select(variables=["Muon_pt", "Muon_eta"]).save_pdf("muons.pdf")` one of
326
+ a subset. Errors keep their type and gain the task's note as for `plots()`; a
327
+ failure while writing a page adds one naming the page and the file.
328
+
329
+ ## Running plots yourself
330
+
331
+ `book.plots()` is a lazy iterator of `(task, plot)` pairs: one figure per
332
+ step, nothing drawn ahead of time. The plots are ordinary
333
+ [`Plot`][rootfig.Plot] objects, so this is the place to adjust a figure
334
+ or keep it open:
335
+
336
+ ```python
337
+ for task, p in book.plots():
338
+ print(task.variable.safe_name, task.selection_name, task.variant_name)
339
+ p.ax.axvline(91.2, color="gray", ls="--")
340
+ p.save(f"plots/{task.stem}.pdf")
341
+ p.close()
342
+ ```
343
+
344
+ A `PlotTask` carries the `variable`, the `selection` (`Cut` or `None`), the
345
+ `selection_name` and `variant_name` (`None` for an axis the book was built
346
+ without), the merged `kwargs` and the file `stem`. Tasks compare and hash by
347
+ their `stem`, so they work as set members and dictionary keys whatever the
348
+ keyword values hold. Each plot is what
349
+
350
+ ```python
351
+ rf.plot(book.data, task.variable, selection=task.selection, **task.kwargs)
352
+ ```
353
+
354
+ returns: the same histograms, binning, systematics, labels and errors.
355
+
356
+ The book only reads less often than that call would. It runs the tasks in
357
+ batches of a few dozen variables: the branches those variables and every
358
+ selection need are read once per sample for the batch, the files a
359
+ `Systematic.samples` variation fills from included, and variants that only
360
+ change the drawing (`logy`, `normalize`, `ratio`, `style`, ...) are drawn from
361
+ one set of prepared histograms, each figure from its own copy. A variant that
362
+ changes how the histograms are prepared (`bins`, `weight`, `observed`,
363
+ `systematics`, ...) is prepared on its own, still from the batch's read.
364
+ Variables that name histograms stored in the files are read with one pass over
365
+ each file per batch. A book with an explicit variable list inspects no input
366
+ when it is built; one built with `rf.ALL` inspects the metadata of its inputs
367
+ then, but never reads event arrays or histogram contents. Neither reads
368
+ anything for `tasks()`; the first batch is read when the first plot is
369
+ requested and the next one when the iteration reaches it.
370
+
371
+ Errors stop the book at the failing task. They keep their type and gain a note
372
+ naming the task, so a traceback for a typo in one expression reads
373
+
374
+ ```text
375
+ rootfig.errors.MissingBranchError: unknown name 'recoil_mas' ...
376
+ while running PlotBook task variable='recoil_mas', selection='sr', variant='log'
377
+ ```
378
+
379
+ The words `all` and `default` in such messages stand for an axis that was not
380
+ given; they are never used to decide a file name.
381
+
382
+ ## Subsets
383
+
384
+ `book.select(variables=..., selections=..., variants=...)` returns a new book
385
+ restricted to the named variables (by `safe_name`), selections and variants
386
+ (by key). Each argument left as `None` keeps its axis, the book's order is kept
387
+ rather than the filter's, and an unknown name raises with the available
388
+ choices:
389
+
390
+ ```python
391
+ book.select(variables=["mass"], variants=["log"]).save("plots/debug")
392
+
393
+ book.select(variables=["foo"])
394
+ # ValueError: unknown variable 'foo'; available: ['mass', 'pt']
395
+ ```
396
+
397
+ A filter that names nothing, `select(variables=[])`, raises as well, rather than
398
+ quietly producing an empty book.
399
+
400
+ An axis the book was built without has the single choice `"all"` (selections)
401
+ or `"default"` (variants); selecting it is a no-op and the axis stays implicit.
402
+
403
+ ## What a book does not do
404
+
405
+ A book runs its tasks one after another in the calling process and keeps the
406
+ arrays of one batch of variables at a time. Its outputs are the individual files
407
+ of `save()` and the compact overview of `save_pdf()`: it has no filename
408
+ template, no parallel execution, and no report generation beyond that grid of
409
+ plots (no captions, tables of contents, headers, HTML or slides). It drives the
410
+ 1D `rf.plot` only: for `plot2d`, `efficiency` and `profile` you write the loop
411
+ yourself.