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.
- {rootfig-0.8.0 → rootfig-0.9.0}/CONTRIBUTING.md +31 -6
- {rootfig-0.8.0 → rootfig-0.9.0}/PKG-INFO +1 -1
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/composable.md +1 -1
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/ecosystem.md +4 -3
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/expressions.md +4 -1
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/plotting.md +28 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/pyproject.toml +3 -2
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/__init__.py +1 -1
- rootfig-0.9.0/src/rootfig/_threads.py +65 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/pipeline.py +203 -61
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/stats.py +17 -6
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/__init__.py +2 -0
- rootfig-0.9.0/src/rootfig/io/compat.py +51 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/sources.py +203 -37
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/binning.py +131 -28
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/selection/__init__.py +3 -0
- rootfig-0.9.0/src/rootfig/selection/chunks.py +148 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/selection/columns.py +67 -11
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/conftest.py +11 -5
- rootfig-0.9.0/tests/data/embedded_basket.root +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_api.py +4 -4
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_discovery.py +1 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_histograms.py +266 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_io.py +265 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_model.py +31 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_selection.py +185 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/.gitignore +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/LICENSE +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/README.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/api.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/batch.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/gallery/index.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/hooks/gallery.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/arrays.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/correlation.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/density_flow.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/efficiency.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/expressions.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/fill_stats.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/hist2d.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/log_axes.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/luminosity-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/luminosity.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/many_plots-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/many_plots.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/object_vs_event.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/overlay_ratio.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/profile.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/pull.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/quick.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/ratio_reference.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/robust_range-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/robust_range.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/selective_stack.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stack_data.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stored_histograms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/stored_histograms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/style_colors-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/style_colors.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/systematics.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/variable_bins.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-alice-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-alice.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-atlas-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-atlas.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-cms-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-cms.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dune-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-dune.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-lhcb-dark.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio-lhcb.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/images/gallery/xbreak_ratio.png +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/index.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/quickstart.md +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/docs/stylesheets/gallery.css +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/__main__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/data.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/examples/gallery/registry.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/mkdocs.yml +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_mapping.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_storage.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/_typing.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_common.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_discover.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_hists.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_panel.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/_pdf.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/batch.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/data.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/measures.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/plots1d.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/plots2d.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/api/tables.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/errors.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/functions.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/expressions/parser.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/build.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/comparison.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/cutflow.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/efficiency.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/groups.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/normalize.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/prefetch.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/sources.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/stored.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/histograms/systematics.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/cache.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/objects.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/io/schema.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/cuts.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/filenames.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/groups.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/inputs.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/samples.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/style.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/systematics.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/units.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/model/variables.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/__init__.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/annotations.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/correlation.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/engine.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/figure.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/finish.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/hist1d.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/hist2d.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/pages.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/panel.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/points.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/result.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/plotting/style.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/src/rootfig/py.typed +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/data/split_collection.root +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_batch.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_expressions.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_gallery.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_pages.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_plotting.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_save_pdf.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/test_tutorials.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/type_checks/batch.py +0 -0
- {rootfig-0.8.0 → rootfig-0.9.0}/tests/type_checks/groups.py +0 -0
- {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.
|
|
66
|
-
committed
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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`.
|
|
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
|
|
33
|
-
#
|
|
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
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
|
|
347
|
-
nominal
|
|
348
|
-
:class:`Columns` to fill, a factor
|
|
349
|
-
for a down direction mirrored from the
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|