pax_global_header00006660000000000000000000000064151662743430014525gustar00rootroot0000000000000052 comment=5f6f3e3cadebe8c624d4254353a12339b70e16a3 scikit-hep-boost-histogram-5f6f3e3/000077500000000000000000000000001516627434300173275ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/.all-contributorsrc000066400000000000000000000156351516627434300231720ustar00rootroot00000000000000{ "projectName": "boost-histogram", "projectOwner": "scikit-hep", "repoType": "github", "repoHost": "https://github.com", "files": [ "README.md" ], "imageSize": 100, "commit": false, "commitConvention": "none", "contributors": [ { "login": "henryiii", "name": "Henry Schreiner", "avatar_url": "https://avatars1.githubusercontent.com/u/4616906?v=4", "profile": "http://iscinumpy.gitlab.io", "contributions": [ "maintenance", "code", "doc" ] }, { "login": "HDembinski", "name": "Hans Dembinski", "avatar_url": "https://avatars0.githubusercontent.com/u/2631586?v=4", "profile": "https://github.com/HDembinski", "contributions": [ "maintenance", "code" ] }, { "login": "LovelyBuggies", "name": "N!no", "avatar_url": "https://avatars3.githubusercontent.com/u/29083689?v=4", "profile": "http://lovelybuggies.github.io", "contributions": [ "test", "doc" ] }, { "login": "jpivarski", "name": "Jim Pivarski", "avatar_url": "https://avatars0.githubusercontent.com/u/1852447?v=4", "profile": "https://github.com/jpivarski", "contributions": [ "ideas" ] }, { "login": "nsmith-", "name": "Nicholas Smith", "avatar_url": "https://avatars3.githubusercontent.com/u/6587412?v=4", "profile": "https://github.com/nsmith-", "contributions": [ "bug" ] }, { "login": "physicscitizen", "name": "physicscitizen", "avatar_url": "https://avatars3.githubusercontent.com/u/45843291?v=4", "profile": "http://www-pnp.physics.ox.ac.uk/~huffman/", "contributions": [ "bug" ] }, { "login": "ChanchalKumarMaji", "name": "Chanchal Kumar Maji", "avatar_url": "https://avatars1.githubusercontent.com/u/31502077?v=4", "profile": "https://www.linkedin.com/in/chanchal-kumar-maji-9230a9145/", "contributions": [ "doc" ] }, { "login": "douglasdavis", "name": "Doug Davis", "avatar_url": "https://avatars2.githubusercontent.com/u/3202090?v=4", "profile": "https://ddavis.io/", "contributions": [ "bug" ] }, { "login": "pgrimaud", "name": "Pierre Grimaud", "avatar_url": "https://avatars1.githubusercontent.com/u/1866496?v=4", "profile": "https://github.com/pgrimaud", "contributions": [ "doc" ] }, { "login": "beojan", "name": "Beojan Stanislaus", "avatar_url": "https://avatars0.githubusercontent.com/u/3727925?v=4", "profile": "https://github.com/beojan", "contributions": [ "bug" ] }, { "login": "Popinaodude", "name": "Popinaodude", "avatar_url": "https://avatars2.githubusercontent.com/u/20911987?v=4", "profile": "https://github.com/Popinaodude", "contributions": [ "bug" ] }, { "login": "colizz", "name": "Congqiao Li", "avatar_url": "https://avatars2.githubusercontent.com/u/44885400?v=4", "profile": "https://github.com/colizz", "contributions": [ "bug" ] }, { "login": "alexander-held", "name": "alexander-held", "avatar_url": "https://avatars0.githubusercontent.com/u/45009355?v=4", "profile": "https://github.com/alexander-held", "contributions": [ "bug" ] }, { "login": "chrisburr", "name": "Chris Burr", "avatar_url": "https://avatars3.githubusercontent.com/u/5220533?v=4", "profile": "https://github.com/chrisburr", "contributions": [ "doc" ] }, { "login": "kgizdov", "name": "Konstantin Gizdov", "avatar_url": "https://avatars.githubusercontent.com/u/3164953?v=4", "profile": "https://keybase.io/kgizdov", "contributions": [ "platform", "bug" ] }, { "login": "cranmer", "name": "Kyle Cranmer", "avatar_url": "https://avatars.githubusercontent.com/u/4458890?v=4", "profile": "http://theoryandpractice.org", "contributions": [ "doc" ] }, { "login": "amangoel185", "name": "Aman Goel", "avatar_url": "https://avatars.githubusercontent.com/u/10528392?v=4", "profile": "http://amangoel.me", "contributions": [ "doc", "code" ] }, { "login": "gohil-jay", "name": "Jay Gohil", "avatar_url": "https://avatars.githubusercontent.com/u/59703162?v=4", "profile": "http://jay-gohil.me", "contributions": [ "doc" ] }, { "login": "pfackeldey", "name": "Peter Fackeldey", "avatar_url": "https://avatars.githubusercontent.com/u/18463582?v=4", "profile": "https://github.com/pfackeldey", "contributions": [ "code" ] }, { "login": "jonas-eschle", "name": "Jonas Eschle", "avatar_url": "https://avatars.githubusercontent.com/u/17454848?v=4", "profile": "https://github.com/jonas-eschle", "contributions": [ "code" ] }, { "login": "Saransh-cpp", "name": "Saransh Chopra", "avatar_url": "https://avatars.githubusercontent.com/u/74055102?v=4", "profile": "https://saransh-cpp.github.io/", "contributions": [ "code" ] }, { "login": "matthewfeickert", "name": "Matthew Feickert", "avatar_url": "https://avatars.githubusercontent.com/u/5142394?v=4", "profile": "http://www.matthewfeickert.com/", "contributions": [ "code" ] }, { "login": "andrzejnovak", "name": "Andrzej Novak", "avatar_url": "https://avatars.githubusercontent.com/u/13226500?v=4", "profile": "http://andrzejnovak.github.io/", "contributions": [ "code" ] }, { "login": "veprbl", "name": "Dmitry Kalinkin", "avatar_url": "https://avatars.githubusercontent.com/u/245573?v=4", "profile": "https://github.com/veprbl", "contributions": [ "code" ] }, { "login": "klieret", "name": "Kilian Lieret", "avatar_url": "https://avatars.githubusercontent.com/u/13602468?v=4", "profile": "https://www.lieret.net/", "contributions": [ "doc" ] }, { "login": "riga", "name": "Marcel Rieger", "avatar_url": "https://avatars.githubusercontent.com/u/1908734?v=4", "profile": "https://github.com/riga", "contributions": [ "code" ] }, { "login": "Superharz", "name": "Florian Harz", "avatar_url": "https://avatars.githubusercontent.com/u/11306732?v=4", "profile": "https://github.com/Superharz", "contributions": [ "code" ] } ], "contributorsPerLine": 7, "skipCi": true, "commitType": "docs" } scikit-hep-boost-histogram-5f6f3e3/.clang-format000066400000000000000000000053761516627434300217150ustar00rootroot00000000000000--- Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false # SplitEmptyFunctionBody: true BreakBeforeBinaryOperators: All BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 88 CommentPragmas: "^ IWYU pragma:" CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false # ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [foreach, Q_FOREACH, BOOST_FOREACH] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: ".*" Priority: 1 IncludeIsMainRegex: "$" IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: "" MacroBlockEnd: "" MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: Never SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never scikit-hep-boost-histogram-5f6f3e3/.clang-tidy000066400000000000000000000012751516627434300213700ustar00rootroot00000000000000FormatStyle: file Checks: " modernize-*, readability-*, bugprone-*, performance-*, portability-*, clang-analyzer-*, misc-*, -bugprone-easily-swappable-parameters, -misc-include-cleaner, -misc-no-recursion, -misc-non-private-member-variables-in-classes, -misc-redundant-expression, -modernize-avoid-c-arrays, -modernize-use-trailing-return-type, -readability-braces-around-statements, -readability-function-cognitive-complexity, -readability-identifier-length, -readability-magic-numbers, -readability-named-parameter, google-explicit-constructor, google-readability-casting, llvm-namespace-comment, " HeaderFilterRegex: "bh_python/.*hpp" UseColor: true scikit-hep-boost-histogram-5f6f3e3/.cmake-format.yaml000066400000000000000000000001601516627434300226340ustar00rootroot00000000000000format: line_width: 99 # Causes a few issues - can be solved later, possibly. markup: enable_markup: false scikit-hep-boost-histogram-5f6f3e3/.gitattributes000066400000000000000000000001161516627434300222200ustar00rootroot00000000000000*.py diff=python *.pyi diff=python *.cpp diff=cpp *.hpp diff=cpp *.svg -diff scikit-hep-boost-histogram-5f6f3e3/.github/000077500000000000000000000000001516627434300206675ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/.github/CONTRIBUTING.md000066400000000000000000000210701516627434300231200ustar00rootroot00000000000000See the [Scikit-HEP Developer introduction][skhep-dev-intro] for a detailed description of best practices for developing Scikit-HEP packages. [skhep-dev-intro]: https://scikit-hep.org/developer/intro # Contributing ## Building from source This repository has dependencies in submodules. Check out the repository like this: ```bash git clone --recursive https://github.com/scikit-hep/boost-histogram.git cd boost-histogram ```
Faster version (click to expand) ```bash git clone https://github.com/scikit-hep/boost-histogram.git cd boost-histogram git submodule update --init --depth 10 ```
## Setting up a development environment ### Nox The fastest way to start with development is to use nox. If you don't have nox, you can use `pipx run nox` to run it without installing, or `pipx install nox`. If you don't have pipx (pip for applications), then you can install with with `pip install pipx` (the only case were installing an application with regular pip is reasonable). If you use macOS, then pipx and nox are both in brew, use `brew install pipx nox`. To use, run `nox`. This will lint and test using every installed version of Python on your system, skipping ones that are not installed. You can also run specific jobs: ```console $ nox -l # List all the defined sessions $ nox -s lint # Lint only $ nox -s tests # Tests only $ nox -s docs -- serve # Build and serve the docs $ nox -s make_pickle # Make a pickle file for this version ``` Nox handles everything for you, including setting up a temporary virtual environment for each run. ### Pip While developers often work in CMake, the "correct" way to develop a python package is in a virtual environment. This is how you would set one up with Python's built-in venv: ```bash python3 -m venv .venv source ./.venv/bin/activate pip install -ve. --group dev ``` Or if you use uv: ```bash uv sync ```
Optional: External Jupyter kernel (click to expand) You can set up a kernel for external Jupyter then deactivate your environment: ```bash python -m ipykernel install --user --name boost-hist deactivate ``` Now, you can run notebooks using your system JupyterLab, and it will list the environment as available!
To rebuild, rerun `pip install -ve .` from the environment, if the commit has changed, you will get a new build. Due to the `-e`, Python changes do not require a rebuild. ### CMake CMake is common for C++ development, and ties nicely to many C++ tools, like IDEs. If you want to use it for building, you can. Make a build directory and run CMake. If you have a specific Python you want to use, add `-DPython_EXECUTABLE=$(which python)` or similar to the CMake line. If you need help installing the latest CMake version, [visit this page](https://cliutils.gitlab.io/modern-cmake/chapters/intro/installing.html); one option is to use pip to install CMake. You have three options for running code in python: 1. Run from the build directory (only works with some commands, like `python -m pytest`, and not others, like `pytest` 2. Add the build directory to your PYTHONPATH environment variable 3. Set `CMAKE_INSTALL_PREFIX` to your site-packages and install (recommended for virtual environments). Here is the recommendation for a CMake install, using uv: ```bash uv venv uv pip install --group dev cmake --workflow default # Option 3 only: cmake --install --preset default --prefix $(python -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_lib(plat_specific=False,standard_lib=False))") ``` Note that option 3 will require reinstalling if the python files change, while options 1-2 will not if you have a recent version of CMake (symlinks are made). ## Testing Run the unit tests (requires pytest and NumPy). ```bash python3 -m pytest ``` For CMake, you can use `ctest --preset default` (the workflow above will run the tests). The build requires `setuptools_scm`. The tests require `numpy`, `pytest`, and `pytest-benchmark`. `pytest-sugar` adds some nice formatting. ## Benchmarking You can enable benchmarking with `--benchmark-enable` when running tests. You can also run explicit performance tests with `scripts/performance_report.py`. ```bash python3 -m pytest --benchmark-enable --benchmark-sort fullname ``` For example, if you want to benchmark before and after a change: ```bash python3 -m pytest --benchmark-enable --benchmark-autosave # Make change python3 -m pytest --benchmark-enable --benchmark-autosave pytest-benchmark compare 0001 0002 --sort fullname --histogram ``` Note, while the histogram option (`--histogram`) is nice, it does require `pygal` and `pygaljs` to be installed. Feel free to leave it off if not needed. ## Formatting Code should be well formatted; CI will check it and one of the authors can help reformat your code. If you want to check it yourself, you should use [`prek`](https://github.com/j178/prek). Just [install prek](https://github.com/j178/prek), probably using brew on macOS or pip on other platforms, then run: ```bash prek install ``` Now all changed files will be checked every time you git commit. You can check it yourself (even without installing the hooks) using: ```bash prek run -a ``` Developers should update the prek dependencies once in a while, you can do this automatically with: ```bash prek auto-update ``` ## Clang-Tidy To run Clang tidy, the following recipe should work. Files will be modified in place, so you can use git to monitor the changes. ```bash docker run --rm -v $PWD:/pybind11 -it silkeh/clang:20 apt update && apt install -y python3-dev python3-pip git ninja-build python3 -m pip install setuptools_scm --break-system-packages cmake --preset tidy cmake --build --preset tidy ``` To autofix, use: ```bash cmake --preset --preset tidy-fix cmake --build --preset tidy-fix ``` We also provide matching `--workflow`'s, but you'll need a newer CMake for that (you can use pip to get it, though). ## Include what you use To run include what you use, install (`brew install include-what-you-use` on macOS), then run: ```bash cmake -S . -B build-iwyu -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=$(which include-what-you-use) cmake --build build ``` ## Timing steps Make time/memory taken can be set `CMAKE_CXX_COMPILER_LAUNCHER`/`CMAKE_CXX_LINKER_LANCHER`. Some examples: ``` # Linux: # "time" # "time;-v" # "time;-f;'%U user %S system %E elapsed %P CPU %M KB'" # macOS: # "time" # macOS with brew install gnu-time: # "gtime;-f;'%U user %S system %E elapsed %P CPU %M KB'" # ``` ## Common tasks
Updating dependencies (click to expand) This will checkout new versions of the dependencies. ```bash nox -s bump_boost -- 1.88.0 ```
Making a new release (click to expand) - Finish merging open PRs that you want in the new version - Add most recent changes to the `docs/CHANGELOG.md` - Sync master with develop using `git checkout master; git merge develop --ff-only` and push - Make sure the `cmake --preset tidy` build runs on master without issues (manually trigger if needed) - Make the GitHub release in the GitHub UI. Copy the changelog entries and links for that version; this has to be done as part of the release and tag procedure for archival tools (Zenodo) to pick them up correctly. - Title should be `"Version "` - Version tag should be `"v" + major + "." + minor + "." + patch`. - GHA will build and send to PyPI for you when you release. - Conda-forge will automatically make a PR to update within an hour or so, and it will merge automatically if it passes.
Making a compiler flamegraph (click to expand) This requires LLVM 9+, and is based on [this post](https://aras-p.info/blog/2019/01/16/time-trace-timeline-flame-chart-profiler-for-Clang/). ```bash brew install llvm # macOS way to get clang-9 python3 -m venv .env_core # general environment (no install will be made) . .env_core/bin/activate pip install dependency-groups pip-install-dependency-groups dev CXX="/usr/local/opt/llvm/bin/clang++" cmake -S . -B build-llvm \ -DCMAKE_CXX_FLAGS="-ftime-trace" \ -DPYTHON_EXECUTABLE=$(which python) cmake --build build-llvm/ ``` Now open a browser with [SpeedScope](https://www.speedscope.app), and load one of the files.
Adding a contributor (click to expand) First, you need to install the [all contributor CLI](https://allcontributors.org/docs/en/cli/installation): ```bash yarn add --dev all-contributors-cli ``` Then, you can add contributors: ```bash yarn all-contributors add henryiii maintenance,code,doc ```
scikit-hep-boost-histogram-5f6f3e3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001516627434300230525ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000004361516627434300254650ustar00rootroot00000000000000--- name: Bug report about: Create a report title: "[BUG] " labels: unverified-bug --- **Describe the bug** **Steps to reproduce** scikit-hep-boost-histogram-5f6f3e3/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005271516627434300250460ustar00rootroot00000000000000blank_issues_enabled: True contact_links: - name: Start a discussion url: https://github.com/scikit-hep/boost-histogram/discussions/new about: Please ask and answer questions here, or propose new ideas. - name: Chat on Gitter url: https://gitter.im/HSF/PyHEP-histogramming about: Ask a very simple question or just say hi. scikit-hep-boost-histogram-5f6f3e3/.github/dependabot.yml000066400000000000000000000003411516627434300235150ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" groups: actions: patterns: - "*" scikit-hep-boost-histogram-5f6f3e3/.github/labeler.yml000066400000000000000000000005131516627434300230170ustar00rootroot00000000000000# Add 'needs changelog` label to any change to code files as long as the `CHANGELOG` hasn't changed # Skip dependabot and pre-commit-ci PRs needs changelog: - all: - changed-files: - all-globs-to-all-files: "!docs/CHANGELOG.md" - base-branch: "^(?!dependabot).*" - base-branch: "^(?!pre-commit-ci).*" scikit-hep-boost-histogram-5f6f3e3/.github/release.yml000066400000000000000000000001261516627434300230310ustar00rootroot00000000000000changelog: exclude: authors: - dependabot[bot] - pre-commit-ci[bot] scikit-hep-boost-histogram-5f6f3e3/.github/workflows/000077500000000000000000000000001516627434300227245ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/.github/workflows/copilot-setup-steps.yml000066400000000000000000000015471516627434300274210ustar00rootroot00000000000000name: "Copilot Setup Steps" permissions: {} on: workflow_dispatch: push: paths: - .github/workflows/copilot-setup-steps.yml pull_request: paths: - .github/workflows/copilot-setup-steps.yml jobs: copilot-setup-steps: name: Copilot Setup Steps runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v6 with: persist-credentials: false submodules: true fetch-depth: 0 - uses: actions/setup-python@v6 with: python-version: "3.x" allow-prereleases: true - uses: astral-sh/setup-uv@v7 - name: Install tooling run: | uv tool install nox uv tool install prek - name: Pre-install checks run: | prek prepare-hooks nox -s pylint --install-only scikit-hep-boost-histogram-5f6f3e3/.github/workflows/emscripten.yaml000066400000000000000000000012151516627434300257600ustar00rootroot00000000000000name: WASM on: workflow_dispatch: pull_request: branches: - master - develop release: types: - published concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-pyodide: name: Pyodide cibuildwheel runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 - uses: pypa/cibuildwheel@v3.4 env: CIBW_PLATFORM: pyodide - name: Upload wheels uses: actions/upload-artifact@v7 with: name: pyodide-wheel path: wheelhouse/*.whl scikit-hep-boost-histogram-5f6f3e3/.github/workflows/examples.yml000066400000000000000000000014541516627434300252710ustar00rootroot00000000000000name: Examples on: workflow_dispatch: pull_request: paths: - "examples/*.py" - "notebooks/*.ipynb" - ".github/workflows/examples.yml" push: branches: - master jobs: examples: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - uses: actions/setup-python@v6 with: python-version: "3.10" - uses: astral-sh/setup-uv@v7 - name: Build and install kernel run: uv run --group examples -m ipykernel install --user --name boost-hist - name: Examples run: for f in examples/*.py; do uv run "$f"; done - name: Notebooks run: uv run jupyter nbconvert --execute --ExecutePreprocessor.timeout=90 --inplace notebooks/*.ipynb scikit-hep-boost-histogram-5f6f3e3/.github/workflows/pr.yml000066400000000000000000000003531516627434300240710ustar00rootroot00000000000000name: "Pull Request Labeler" on: - pull_request_target jobs: triage: runs-on: ubuntu-slim steps: - uses: actions/labeler@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} sync-labels: true scikit-hep-boost-histogram-5f6f3e3/.github/workflows/tests.yml000066400000000000000000000070171516627434300246160ustar00rootroot00000000000000name: Tests on: workflow_dispatch: pull_request: push: branches: - master - develop env: FORCE_COLOR: 3 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: clang-tidy: name: Clang-Tidy runs-on: ubuntu-latest container: silkeh/clang:20 steps: - name: Install requirements run: apt-get update && apt-get install -y python3-dev python3-pip git ninja-build - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - name: Install extra requirements run: python3 -m pip install setuptools_scm --break-system-packages - name: Configure run: cmake --preset tidy - name: Build run: cmake --build --preset tidy pylint: runs-on: ubuntu-latest name: PyLint steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - name: Run PyLint run: pipx run nox[uv] -s pylint -- --output-format=github cmake: name: CMake � ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - python-version: "3.10" cmake-extras: "-DCMAKE_CXX_STANDARD=17" - python-version: "3.11" - python-version: "3.14" - python-version: "3.14t" - python-version: "pypy3.11" steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - uses: rui314/setup-mold@v1 - uses: astral-sh/setup-uv@v7 - uses: hendrikmuhs/ccache-action@v1.2 with: key: ${{ matrix.python-version }} create-symlink: true - name: Install python tools run: uv pip install --system --python=python --group github - name: Configure run: cmake --preset default ${{ matrix.cmake-extras }} - name: Build run: cmake --build --preset default - name: Test run: ctest --preset default -j 4 build_wheels: name: ${{ matrix.only }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest only: cp314t-manylinux_x86_64 - os: ubuntu-24.04-arm only: cp313-manylinux_aarch64 - os: windows-latest only: cp310-win32 - os: windows-latest only: cp313-win_amd64 - os: macos-15-intel only: cp314t-macosx_x86_64 - os: macos-latest only: cp312-macosx_arm64 - os: windows-11-arm only: cp311-win_arm64 - os: ubuntu-latest only: gp311_242-manylinux_x86_64 steps: - uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 - uses: astral-sh/setup-uv@v7 - uses: pypa/cibuildwheel@v3.4 with: only: "${{ matrix.only }}" - uses: actions/upload-artifact@v7 with: path: wheelhouse/* name: test-wheels-${{ strategy.job-index }} - name: Check wheels run: pipx run twine check wheelhouse/* shell: bash pass: if: always() needs: [clang-tidy, pylint, cmake, build_wheels] runs-on: ubuntu-slim steps: - uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} scikit-hep-boost-histogram-5f6f3e3/.github/workflows/weekly.yml000066400000000000000000000012551516627434300247520ustar00rootroot00000000000000name: Weekly update on: workflow_dispatch: schedule: - cron: "0 12 * * 0" jobs: basic: name: Head on Python 3.13 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: true # Note: you need to add a git pull here if you are not on a clean checkout, but CI always is - name: Update to latest run: git submodule foreach "git checkout develop || git checkout master" - uses: actions/setup-python@v6 with: python-version: "3.13" - name: Build and install wheel run: | pip install -v . --group test - name: Test run: python -m pytest scikit-hep-boost-histogram-5f6f3e3/.github/workflows/wheels.yml000066400000000000000000000126071516627434300247440ustar00rootroot00000000000000name: Wheels on: workflow_dispatch: inputs: overrideVersion: description: Manually force a version release: types: - published pull_request: paths: - .github/workflows/wheels.yml schedule: - cron: "34 3 * * *" permissions: actions: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.overrideVersion }} jobs: build_sdist: name: Build SDist runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - name: Build SDist run: pipx run build --sdist - name: Check metadata run: pipx run twine check --strict dist/*.tar.gz - uses: actions/upload-artifact@v7 with: path: dist/* name: wheels-sdist build_wheels: name: ${{ matrix.build }} ${{ matrix.arch }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: macos-15-intel arch: auto64 build: "*" - os: macos-latest arch: auto64 build: "*" - os: windows-latest arch: auto64 build: "cp*" - os: windows-latest arch: auto64 build: "{p,g}p*" - os: windows-latest arch: auto32 build: "*" - os: windows-11-arm arch: auto build: "*" - os: ubuntu-latest arch: auto64 build: "*manylinux*" - os: ubuntu-latest arch: auto64 build: "*musllinux*" - os: ubuntu-24.04-arm arch: auto64 build: "*manylinux*" - os: ubuntu-24.04-arm arch: auto64 build: "*musllinux*" steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - uses: astral-sh/setup-uv@v7 - uses: pypa/cibuildwheel@v3.4 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS: ${{ matrix.arch }} - name: Verify clean directory run: git diff --exit-code shell: bash - name: Upload wheels uses: actions/upload-artifact@v7 with: path: wheelhouse/*.whl name: wheels-${{ strategy.job-index }} build_ios_wheels: name: iOS ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: runs-on: [macos-latest, macos-15-intel] steps: - uses: actions/checkout@v6 with: fetch-depth: 0 submodules: true - run: brew uninstall cmake; brew install cmake - uses: pypa/cibuildwheel@v3.4 env: CIBW_PLATFORM: ios - name: Verify clean directory run: git diff --exit-code shell: bash - name: Upload wheels uses: actions/upload-artifact@v7 with: path: wheelhouse/*.whl name: wheels-ios-${{ matrix.runs-on }} # build_android_wheels: # name: Android ${{ matrix.runs-on }} # runs-on: ${{ matrix.runs-on }} # strategy: # fail-fast: false # matrix: # runs-on: [ubuntu-latest, macos-latest] # steps: # - uses: actions/checkout@v6 # with: # fetch-depth: 0 # submodules: true # # - uses: mhsmith/cibuildwheel@android # env: # CIBW_PLATFORM: android # # - name: Verify clean directory # run: git diff --exit-code # shell: bash # # - name: Upload wheels # uses: actions/upload-artifact@v7 # with: # path: wheelhouse/*.whl # name: wheels-android-${{ matrix.runs-on }} upload_all: name: Upload if release needs: [build_wheels, build_ios_wheels, build_sdist] runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' environment: name: pypi url: https://pypi.org/p/boost-histogram permissions: id-token: write attestations: write contents: read steps: - uses: actions/download-artifact@v8 with: pattern: wheels-* merge-multiple: true path: dist - name: List all files run: ls -lh dist - name: Generate artifact attestation for sdist and wheels uses: actions/attest-build-provenance@v4 with: subject-path: "dist/boost_histogram-*" - uses: pypa/gh-action-pypi-publish@release/v1 upload_nightly_wheels: name: Upload nightly wheels to Anaconda Cloud if: | (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && !github.event.repository.fork && github.ref == 'refs/heads/develop' needs: [build_wheels] runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v8 with: pattern: wheels-* merge-multiple: true path: dist - name: List all files run: ls -lh dist - name: Upload wheel to Anaconda Cloud as nightly uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3 with: artifacts_path: dist anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} scikit-hep-boost-histogram-5f6f3e3/.gitignore000066400000000000000000000017161516627434300213240ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # Build directories /*build* /docs/_build/* /examples/*/*build* /pip-wheel-metadata/* # Outputs /examples/*.png /examples/*.pkl /tests/*.pkl /*.pkl /notebooks/*.pkl # Python binary files *.pyc *__pycache__* *.egg-info .ipynb_checkpoints /wheelhouse/* /dist/* *.mypy_cache # Other editor files *.swp *~ *.clangd /.cache/* compile_commands.json # Venvs such as /.env /*env* # Generated version /src/boost_histogram/version.py # Allow all contributors to be installed /node_modules/* /yarn.lock /package.json /package-lock.json # Testing /.benchmarks/* /.hypothesis/* # PyCharm /.idea/* # VSCode /.vscode/* # Cache /emsdk-cache/* # Nox .nox/* /*venv* /build .DS_Store # Locking uv.lock *pylock scikit-hep-boost-histogram-5f6f3e3/.gitmodules000066400000000000000000000011431516627434300215030ustar00rootroot00000000000000[submodule "extern/histogram"] path = extern/histogram url = ../../boostorg/histogram.git [submodule "extern/core"] path = extern/core url = ../../boostorg/core.git [submodule "extern/mp11"] path = extern/mp11 url = ../../boostorg/mp11.git [submodule "extern/config"] path = extern/config url = ../../boostorg/config.git [submodule "extern/throw_exception"] path = extern/throw_exception url = ../../boostorg/throw_exception.git [submodule "extern/assert"] path = extern/assert url = ../../boostorg/assert.git [submodule "extern/variant2"] path = extern/variant2 url = ../../boostorg/variant2.git scikit-hep-boost-histogram-5f6f3e3/.pre-commit-config.yaml000066400000000000000000000046201516627434300236120ustar00rootroot00000000000000ci: autoupdate_commit_msg: "chore(deps): update pre-commit hooks" autofix_commit_msg: "style: pre-commit fixes" autoupdate_schedule: monthly repos: - repo: https://github.com/adamchainz/blacken-docs rev: "1.20.0" hooks: - id: blacken-docs additional_dependencies: [black==25.*] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/cheshirekow/cmake-format-precommit rev: v0.6.13 hooks: - id: cmake-format additional_dependencies: [pyyaml] - repo: https://github.com/rbubley/mirrors-prettier rev: "v3.8.1" hooks: - id: prettier types_or: [yaml, markdown, html, css, scss, javascript, json] - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.15.9" hooks: - id: ruff-check args: ["--fix", "--show-fixes"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.20.0 hooks: - id: mypy files: ^(src|examples) additional_dependencies: [numpy~=2.4.0, pytest, uhi] - repo: https://github.com/codespell-project/codespell rev: v2.4.2 hooks: - id: codespell args: ["-L", "hist,nd,circularly,ba,fo", "-w"] exclude: ^(notebooks/xarray.ipynb|notebooks/BoostHistogramHandsOn.ipynb|.*\.svg)$ - repo: local hooks: - id: disallow-caps name: Disallow improper capitalization language: pygrep entry: PyBind|Numpy|Cmake|CCache|Github|PyTest exclude: .pre-commit-config.yaml - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.2 hooks: - id: clang-format types_or: [c++] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.37.1 hooks: - id: check-readthedocs - id: check-github-workflows - repo: https://github.com/henryiii/validate-pyproject-schema-store rev: 2026.04.03 hooks: - id: validate-pyproject scikit-hep-boost-histogram-5f6f3e3/.readthedocs.yml000066400000000000000000000007511516627434300224200ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 build: os: "ubuntu-22.04" tools: python: "3.12" commands: - asdf plugin add uv - asdf install uv latest - asdf global uv latest - uv venv - uv pip install --group docs - .venv/bin/sphinx-build -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html submodules: exclude: all scikit-hep-boost-histogram-5f6f3e3/AGENTS.md000066400000000000000000000043221516627434300206330ustar00rootroot00000000000000# boost-histogram Agent Guide ## Build System **Hybrid Python/C++ project** using pybind11. Two build paths: - **Python (recommended for most work):** `uv sync` or `pip install -ve .` - **C++ development:** `cmake --preset default` then `cmake --build --preset default` You can also prefix commands with `uv run` (recommended), this builds as needed. Git submodules required for C++ build: ```bash git clone --recursive https://github.com/scikit-hep/boost-histogram.git # or if already cloned: git submodule update --init --depth 10 ``` ## Commands | Task | Command | | --------------------------- | -------------------------- | | Lint, format, and typecheck | `prek -a` | | Tests only | `uv run pytest` | | Build docs + serve | `nox -s docs -- serve` | | CMake build + test | `cmake --workflow default` | ## Project Structure ``` src/boost_histogram/ # Python package src/*.cpp # pybind11 bindings extern/histogram/ # Boost.Histogram C++ headers (git submodule) tests/ # pytest suite ``` Key entry points: - `src/boost_histogram/__init__.py` - Main Python API - `src/module.cpp` - pybind11 module definition ## Testing Quirks - Requires `pytest-benchmark` plugin (in `dev` dependency group) - Use `--benchmark-disable` to skip benchmarks in CI - Hypothesis tests may need `CI=1` env var on some systems to pass health checks - Run single test: `python -m pytest tests/test_file.py::test_function` ## Toolchain Notes - **Python:** 3.10+ required, 3.14t (free-threading) supported - **C++:** C++14 standard, gcc >= 5.5, clang >= 3.8, or MSVC >= 14.1 - **Build backend:** scikit-build-core + pybind11 - **Formatter:** ruff (lint + format), clang-format (C++) - **Type checker:** mypy (strict mode on src/) ## Versioning - Dynamic version from git tags via `setuptools_scm` - Version generated at build time in `boost_histogram/version.py` ## Related Projects - [Hist](https://github.com/scikit-hep/hist) - High-level wrapper extending boost-histogram - [UHI](https://uhi.readthedocs.io) - Histogram interoperability spec - [mplhep](https://mplhep.readthedocs.io) - Plotting with UHI support scikit-hep-boost-histogram-5f6f3e3/CMakeLists.txt000066400000000000000000000173371516627434300221020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.24...3.31) project(BOOST_HISTOGRAM LANGUAGES CXX) # Version is added later include(FetchContent) # Boost histogram requires C++14 set(CMAKE_CXX_STANDARD 14 CACHE STRING "The C++ standard to compile with, 14+") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Adding folders to keep the structure a bit nicer in IDE's set_property(GLOBAL PROPERTY USE_FOLDERS ON) # This will force color output at build time if this env variable is set _at # configure time_. This is useful for CI. if($ENV{FORCE_COLOR}) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") add_compile_options(-fdiagnostics-color=always) elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") add_compile_options(-fcolor-diagnostics) endif() endif() # This is a standard recipe for setting a default build type set(_default_build_type "Debug") if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${_default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${_default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() # If no Python venv set, and .venv exists, use it. if(NOT DEFINED ENV{VIRTUALENV} AND NOT DEFINED Python_ROOT_DIR AND NOT DEFINED Python_EXECUTABLE AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.venv") set(Python_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.venv") endif() # Adding pybind11 and setting up Python # Will display pybind11 version set(PYBIND11_FINDPYTHON TRUE) set(Python_ARTIFACTS_INTERACTIVE TRUE) FetchContent_Declare( pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11.git GIT_TAG bd67643652d3800837f1f41549a2a5adbaa3fafe # v2.13.3 FIND_PACKAGE_ARGS NAMES pybind11) FetchContent_MakeAvailable(pybind11) # Display versions message(STATUS "CMake ${CMAKE_VERSION}") message(STATUS "Python ${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}") if(DEFINED pybind11_VERSION) message(STATUS "pybind11 ${pybind11_VERSION}") endif() # This is completely optional and just adds hints to IDEs - no affect on build at all. file(GLOB_RECURSE BOOST_HIST_FILES "extern/histogram/include/*.hpp") file(GLOB_RECURSE BOOST_HIST_PY_HEADERS "include/bh_python/*.hpp") # List the source files for the Python extension # On some backends (like make), this will regenerate if a file was added/removed file(GLOB BOOST_HIST_PY_SRC CONFIGURE_DEPENDS src/*.cpp) # This is the Python module pybind11_add_module(_core MODULE ${BOOST_HIST_PY_HEADERS} ${BOOST_HIST_PY_SRC} ${BOOST_HIST_FILES}) # Add the include directory for boost/histogram/python target_include_directories(_core PRIVATE include) # These are the Boost header-only libraries required by Boost::Histogram target_include_directories( _core SYSTEM PUBLIC extern/assert/include extern/config/include extern/core/include extern/histogram/include extern/mp11/include extern/throw_exception/include extern/variant2/include) # Support older Windows from newer MSVC target_compile_options(_core PRIVATE $<$:/d2FH4->) # Better error messages target_compile_definitions(_core PRIVATE PYBIND11_DETAILED_ERROR_MESSAGES) # Atomic is required for armv7l if(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7l") target_link_libraries(_core PRIVATE atomic) endif() # This makes IDE's like XCode mimic the Boost Histogram structure source_group( TREE ${CMAKE_CURRENT_SOURCE_DIR}/extern/histogram/include PREFIX "Header Files" FILES ${BOOST_HIST_FILES}) source_group( TREE ${CMAKE_CURRENT_SOURCE_DIR}/include PREFIX "Header Files" FILES ${BOOST_HIST_PY_HEADERS}) source_group( TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "Source Files" FILES ${BOOST_HIST_PY_SRC}) # Cause warnings to be errors (not recommended for MSVC, since pybind11 might cause a few there) option(BOOST_HISTOGRAM_ERRORS "Make warnings errors (for CI mostly)") # Adding warnings # Boost.Histogram doesn't pass sign -Wsign-conversion if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") target_compile_options( _core PRIVATE -Wall -Wextra -pedantic-errors -Wconversion -Wsign-compare -Wno-unused-value -Wno-sign-conversion) if(BOOST_HISTOGRAM_ERRORS) target_compile_options(_core PRIVATE -Werror) endif() elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") target_compile_options(_core PRIVATE /W4) if(BOOST_HISTOGRAM_ERRORS) target_compile_options(_core PRIVATE /WE) endif() endif() # This allows setting the maximum number of axes in a histogram set(BOOST_HISTOGRAM_DETAIL_AXES_LIMIT "" CACHE STRING "Set the maximum number of axis in a histogram (affects compile time and size)") if(NOT "${BOOST_HISTOGRAM_DETAIL_AXES_LIMIT}" STREQUAL "") target_compile_definitions( _core PRIVATE BOOST_HISTOGRAM_DETAIL_AXES_LIMIT=${BOOST_HISTOGRAM_DETAIL_AXES_LIMIT}) endif() # Make the output library be in boost_histogram/_core... # This is a generator expression to avoid Debug/Release subdirectories in IDEs, # which confuses Python. set_property(TARGET _core PROPERTY LIBRARY_OUTPUT_DIRECTORY "$<1:boost_histogram>") # Collect all the python files and symlink them into the build directory # Protects from in-source builds (don't do this, please) if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}" AND NOT DEFINED SKBUILD) file( GLOB_RECURSE BOOST_HIST_PY_FILES LIST_DIRECTORIES false RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" CONFIGURE_DEPENDS "src/boost_histogram/*.py") foreach(F IN LISTS BOOST_HIST_PY_FILES) file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/${F}") file(CREATE_LINK "${CMAKE_CURRENT_SOURCE_DIR}/src/${F}" "${CMAKE_CURRENT_BINARY_DIR}/${F}" COPY_ON_ERROR SYMBOLIC) endforeach() endif() # Support installing install(TARGETS _core DESTINATION "boost_histogram") if(NOT DEFINED SKBUILD) install(DIRECTORY "src/boost_histogram" DESTINATION ".") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/boost_histogram/version.py" DESTINATION "boost_histogram") # Tests (Requires pytest to be available to run) include(CTest) endif() if(DEFINED SKBUILD) # Don't worry about the version elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/boost_histogram/version.py") set(VERSION_REGEX [=[version[ \t]*=[ \t]*["']([0-9]+\.[0-9]+\.[0-9]+)]=]) # Read in the line containing the version file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/src/boost_histogram/version.py" VERSION_STRING REGEX [=[version[ \t]*=]=]) # Pick out just the version string(REGEX MATCH [=[[0-9]+\.[0-9]+\.[0-9]+]=] VERSION_STRING "${VERSION_STRING}") else() pybind11_find_import(setuptools_scm REQUIRED) execute_process( COMMAND ${Python_EXECUTABLE} -c "from setuptools_scm import get_version; print(get_version())" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE VERSION_FULL_STRING OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) string(REGEX MATCH [=[^[0-9]+\.[0-9]+\.[0-9]+]=] VERSION_STRING "${VERSION_FULL_STRING}") string(REPLACE "-" "." VERSION_STRING "${VERSION_STRING}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/boost_histogram/version.py" "version = '${VERSION_FULL_STRING}'") message(STATUS "Full version output: ${VERSION_FULL_STRING}") endif() if(NOT DEFINED SKBUILD) project( BOOST_HISTOGRAM LANGUAGES CXX VERSION ${VERSION_STRING}) message(STATUS "boost-histogram ${BOOST_HISTOGRAM_VERSION}") if(BUILD_TESTING) add_subdirectory(tests) endif() endif() scikit-hep-boost-histogram-5f6f3e3/CMakePresets.json000066400000000000000000000042651516627434300225570ustar00rootroot00000000000000{ "version": 6, "configurePresets": [ { "name": "default", "displayName": "Default configure", "generator": "Ninja", "binaryDir": "build", "warnings": { "dev": true, "deprecated": true }, "cacheVariables": { "BOOST_HISTOGRAM_ERRORS": "ON", "CMAKE_BUILD_TYPE": "Debug", "CMAKE_EXPORT_COMPILE_COMMANDS": true } }, { "name": "tidy", "displayName": "Clang-tidy", "inherits": "default", "binaryDir": "build-tidy", "cacheVariables": { "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--warnings-as-errors=*" } }, { "name": "tidy-fix", "displayName": "Clang-tidy autofix", "inherits": "tidy", "cacheVariables": { "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--warnings-as-errors=*;--fix" } } ], "buildPresets": [ { "name": "default", "displayName": "Default build", "verbose": true, "configurePreset": "default" }, { "name": "tidy", "displayName": "Clang-tidy build", "configurePreset": "tidy", "nativeToolOptions": ["-k0"] }, { "name": "tidy-fix", "displayName": "Clang-tidy autofix build", "configurePreset": "tidy-fix", "jobs": 1, "nativeToolOptions": ["-k0"] } ], "testPresets": [ { "name": "default", "displayName": "Default test", "configurePreset": "default", "output": { "outputOnFailure": true } } ], "workflowPresets": [ { "name": "default", "displayName": "Default workflow", "steps": [ { "type": "configure", "name": "default" }, { "type": "build", "name": "default" }, { "type": "test", "name": "default" } ] }, { "name": "tidy", "displayName": "Clang-tidy workflow", "steps": [ { "type": "configure", "name": "tidy" }, { "type": "build", "name": "tidy" } ] }, { "name": "tidy-fix", "displayName": "Clang-tidy autofix workflow", "steps": [ { "type": "configure", "name": "tidy-fix" }, { "type": "build", "name": "tidy-fix" } ] } ] } scikit-hep-boost-histogram-5f6f3e3/LICENSE000066400000000000000000000030031516627434300203300ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2018-2019, Henry Schreiner and Hans Dembinski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. scikit-hep-boost-histogram-5f6f3e3/README.md000066400000000000000000000635661516627434300206260ustar00rootroot00000000000000boost-histogram logo # boost-histogram for Python [![Actions Status][actions-badge]][actions-link] [![Documentation Status][rtd-badge]][rtd-link] [![PyPI version][pypi-version]][pypi-link] [![Conda-Forge][conda-badge]][conda-link] [![PyPI platforms][pypi-platforms]][pypi-link] [![DOI](https://zenodo.org/badge/148885351.svg)](https://zenodo.org/badge/latestdoi/148885351) [![GitHub Discussion][github-discussions-badge]][github-discussions-link] [![Gitter][gitter-badge]][gitter-link] [![Scikit-HEP][sk-badge]](https://scikit-hep.org/) [![SPEC 4 — Using and Creating Nightly Wheels][spec4-badge]][spec4-link] Python bindings for [Boost::Histogram][] ([source][Boost::Histogram source]), a C++14 library. This is one of the [fastest libraries][] for histogramming, while still providing the power of a full histogram object. See [what's new](./docs/CHANGELOG.md). Other members of the boost-histogram family include: - [Hist][]: The first-party analyst-friendly histogram library that extends boost-histogram with named axes, many new shortcuts including UHI+, plotting shortcuts, and more. - [UHI][]: Specification for Histogram library interop, especially for plotting. - [mplhep][]: Plotting extension for matplotlib with support for UHI histograms. - [histoprint][]: Histogram display library for the command line with support for UHI. - [dask-histogram][]: Dask support for boost-histogram. [uhi]: https://uhi.readthedocs.io [dask-histogram]: https://dask-histogram.readthedocs.io/en/stable/ [mplhep]: https://mplhep.readthedocs.io/en/latest/ [histoprint]: https://github.com/scikit-hep/histoprint ## Usage ![Slideshow of features. See expandable text below if the image is not readable.](https://github.com/scikit-hep/boost-histogram/raw/develop/docs/_images/banner.gif)
Text intro (click to expand) ```python import boost_histogram as bh # Compose axis however you like; this is a 2D histogram hist = bh.Histogram( bh.axis.Regular(2, 0, 1), bh.axis.Regular(4, 0.0, 1.0), ) # Filling can be done with arrays, one per dimension hist.fill( [0.3, 0.5, 0.2], [0.1, 0.4, 0.9], ) # NumPy array view into histogram counts, no overflow bins values = hist.values() # Make a new histogram with just the second axis, summing over the first, and # rebinning the second into larger bins: h2 = hist[::sum, :: bh.rebin(2)] ```
We support the [uhi][] [PlottableHistogram][] protocol, so boost-histogram/[Hist][] histograms can be plotted via any compatible library, such as [mplhep][]. [uhi]: https://github.com/scikit-hep/uhi [PlottableHistogram]: https://uhi.readthedocs.io/en/latest/plotting.html [mplhep]: https://github.com/scikit-hep/mplhep ## Cheatsheet
Simplified list of features (click to expand) - Many axis types (all support `metadata=...`) - `bh.axis.Regular(n, start, stop, ...)`: Make a regular axis. Options listed below. - `overflow=False`: Turn off overflow bin - `underflow=False`: Turn off underflow bin - `growth=True`: Turn on growing axis, bins added when out-of-range items added - `circular=True`: Turn on wrapping, so that out-of-range values wrap around into the axis - `transform=bh.axis.transform.Log`: Log spacing - `transform=bh.axis.transform.Sqrt`: Square root spacing - `transform=bh.axis.transform.Pow(v)`: Power spacing - See also the flexible [Function transform](https://boost-histogram.readthedocs.io/en/latest/usage/transforms.html) - `bh.axis.Integer(start, stop, *, underflow=True, overflow=True, growth=False, circular=False)`: Special high-speed version of `regular` for evenly spaced bins of width 1 - `bh.axis.Variable([start, edge1, edge2, ..., stop], *, underflow=True, overflow=True, circular=False)`: Uneven bin spacing - `bh.axis.IntCategory([...], *, growth=False)`: Integer categories - `bh.axis.StrCategory([...], *, growth=False)`: String categories - `bh.axis.Boolean()`: A True/False axis - Axis features: - `.index(value)`: The index at a point (or points) on the axis - `.value(index)`: The value for a fractional bin (or bins) in the axis - `.bin(i)`: The bin edges (continuous axis) or a bin value (discrete axis) - `.centers`: The N bin centers (if continuous) - `.edges`: The N+1 bin edges (if continuous) - `.extent`: The number of bins (including under/overflow) - `.metadata`: Anything a user wants to store - `.traits`: The options set on the axis - `.size`: The number of bins (not including under/overflow) - `.widths`: The N bin widths - Many storage types - `bh.storage.Double()`: Doubles for weighted values (default) - `bh.storage.Int64()`: 64-bit unsigned integers - `bh.storage.Unlimited()`: Starts small, but can go up to unlimited precision ints or doubles. - `bh.storage.AtomicInt64()`: Threadsafe filling, experimental. Does not support growing axis in threads. - `bh.storage.Weight()`: Stores a weight and sum of weights squared. - `bh.storage.Mean()`: Accepts a sample and computes the mean of the samples (profile). - `bh.storage.WeightedMean()`: Accepts a sample and a weight. It computes the weighted mean of the samples. - Accumulators - `bh.accumulator.Sum`: High accuracy sum (Neumaier) - used by the sum method when summing a numerical histogram - `bh.accumulator.WeightedSum`: Tracks a weighted sum and variance - `bh.accumulator.Mean`: Running count, mean, and variance (Welfords's incremental algorithm) - `bh.accumulator.WeightedMean`: Tracks a weighted sum, mean, and variance (West's incremental algorithm) - Histogram operations - `h.ndim`: The number of dimensions - `h.size or len(h)`: The number of bins - `+`: Add two histograms (storages must match types currently) - `*=`: Multiply by a scaler (not all storages) (`hist * scalar` and `scalar * hist` supported too) - `/=`: Divide by a scaler (not all storages) (`hist / scalar` supported too) - `.kind`: Either `bh.Kind.COUNT` or `bh.Kind.MEAN`, depending on storage - `.storage_type`: Fetch the histogram storage type - `.sum(flow=False)`: The total count of all bins - `.project(ax1, ax2, ...)`: Project down to listed axis (numbers). Can also reorder axes. - `.to_numpy(flow=False, view=False)`: Convert to a NumPy style tuple (with or without under/overflow bins) - `.view(flow=False)`: Get a view on the bin contents (with or without under/overflow bins) - `.values(flow=False)`: Get a view on the values (counts or means, depending on storage) - `.variances(flow=False)`: Get the variances if available - `.counts(flow=False)`: Get the effective counts for all storage types - `.reset()`: Set counters to 0 (growing axis remain the same size) - `.empty(flow=False)`: Check to see if the histogram is empty (can check flow bins too if asked) - `.copy(deep=False)`: Make a copy of a histogram - `.axes`: Get the axes as a tuple-like (all properties of axes are available too) - `.axes[0]`: Get the 0th axis - `.axes.edges`: The lower values as a broadcasting-ready array - `.axes.centers`: The centers of the bins broadcasting-ready array - `.axes.widths`: The bin widths as a broadcasting-ready array - `.axes.metadata`: A tuple of the axes metadata - `.axes.size`: A tuple of the axes sizes (size without flow) - `.axes.extent`: A tuple of the axes extents (size with flow) - `.axes.bin(*args)`: Returns the bin edges as a tuple of pairs (continuous axis) or values (describe) - `.axes.index(*args)`: Returns the bin index at a value for each axis - `.axes.value(*args)`: Returns the bin value at an index for each axis - Indexing - Supports [UHI Indexing](https://uhi.readthedocs.io/en/latest/indexing.html) - Bin content access / setting - `v = h[b]`: Access bin content by index number - `v = h[{0:b}]`: All actions can be represented by `axis:item` dictionary instead of by position (mostly useful for slicing) - Slicing to get histogram or set array of values - `h2 = h[a:b]`: Access a slice of a histogram, cut portions go to flow bins if present - `h2 = h[:, ...]`: Using `:` and `...` supported just like NumPy - `h2 = h[::sum]`: Third item in slice is the "action" - `h[...] = array`: Set the bin contents, either include or omit flow bins - Special accessors - `bh.loc(v)`: Supply value in axis coordinates instead of bin number - `bh.underflow`: The underflow bin (use empty beginning on slice for slicing instead) - `bh.overflow`: The overflow bin (use empty end on slice for slicing instead) - Special actions (third item in slice) - `sum`: Remove axes via projection; if limits are given, use those - `bh.rebin(n)`: Rebin an axis - NumPy compatibility - `bh.numpy` provides faster [drop in replacements](https://boost-histogram.readthedocs.io/en/latest/usage/numpy.html) for NumPy histogram functions - Histograms follow the buffer interface, and provide `.view()` - Histograms can be converted to NumPy style output tuple with `.to_numpy()` - Details - All objects support copy/deepcopy/pickle - Fully statically typed, tested with MyPy.
## Installation You can install this library from [PyPI](https://pypi.org/project/boost-histogram/) with pip: ```bash python3 -m pip install boost-histogram ``` All the normal best-practices for Python apply; Pip should not be very old (Pip 9 is very old), you should be in a virtual environment, etc. Python 3.10+ is required; for older versions of Python (3.5 and 2.7), `0.13` will be installed instead, which is API equivalent to 1.0, but will not be gaining new features. 1.3.x was the last series to support Python 3.6. 1.4.x was the last series to support Python 3.7. 1.5.x was the last series to support Python 3.8. 1.6.x was the last to support Python 3.9. #### Binaries available: The easiest way to get boost-histogram is to use a binary wheel, which happens when you run the above command on a supported platform. Wheels are produced using [cibuildwheel](https://cibuildwheel.readthedocs.io/en/stable/); all common platforms have wheels provided in boost-histogram: | System | Arch | Python versions | PyPy versions | GraalPy versions | | --------- | ------ | ------------------------------------------ | ------------- | ---------------- | | manylinux | 64-bit | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | 3.11 | 24.2, 25.0 | | manylinux | ARM64 | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | 3.11 | 24.2, 25.0 | | musllinux | 64-bit | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | | | | macOS | 64-bit | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | 3.11 | 24.2, 25.0 | | macOS | Arm64 | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | 3.11 | 24.2, 25.0 | | Windows | 32-bit | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | | | | Windows | 64-bit | 3.10, 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | 3.11 | 24.2, 25.0 | | Windows | ARM64 | 3.11, 3.12, 3.13, 3.13t, 3.14, 3.14t | | | | iOS | Device | 3.13, 3.14 | | | | iOS | AS Sim | 3.13, 3.14 | | | | iOS | Intel | 3.13, 3.14 | | | PowerPC, IBM-Z, and RISC-V wheels are not provided but are available on request. If you are on a Linux system that is not part of the "many" in manylinux or musl in musllinux, such as ClearLinux, building from source is usually fine, since the compilers on those systems are often quite new. It will just take longer to install when it is using the sdist instead of a wheel. All dependencies are header-only and included. #### Conda-Forge The boost-histogram package is available on [conda-forge](https://github.com/conda-forge/boost-histogram-feedstock), as well. All supported variants are available. ```bash conda install -c conda-forge boost-histogram ``` #### Source builds For a source build, for example from an "SDist" package, the only requirements are a C++14 compatible compiler. The compiler requirements are dictated by Boost.Histogram's C++ requirements: gcc >= 5.5, clang >= 3.8, or msvc >= 14.1. Boost is not required or needed (this only depends on included header-only dependencies). You can install directly from GitHub if you would like. ```bash python -m pip install git+https://github.com/scikit-hep/boost-histogram.git@develop ``` ## Developing See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on how to set up a development environment. ## Contributors We would like to acknowledge the contributors that made this project possible ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
Henry Schreiner
Henry Schreiner

🚧 💻 📖
Hans Dembinski
Hans Dembinski

🚧 💻
N!no
N!no

⚠� 📖
Jim Pivarski
Jim Pivarski

🤔
Nicholas Smith
Nicholas Smith

�
physicscitizen
physicscitizen

�
Chanchal Kumar Maji
Chanchal Kumar Maji

📖
Doug Davis
Doug Davis

�
Pierre Grimaud
Pierre Grimaud

📖
Beojan Stanislaus
Beojan Stanislaus

�
Popinaodude
Popinaodude

�
Congqiao Li
Congqiao Li

�
alexander-held
alexander-held

�
Chris Burr
Chris Burr

📖
Konstantin Gizdov
Konstantin Gizdov

📦 �
Kyle Cranmer
Kyle Cranmer

📖
Aman Goel
Aman Goel

📖 💻
Jay Gohil
Jay Gohil

📖
Peter Fackeldey
Peter Fackeldey

💻
Jonas Eschle
Jonas Eschle

💻
Saransh Chopra
Saransh Chopra

💻
Matthew Feickert
Matthew Feickert

💻
Andrzej Novak
Andrzej Novak

💻
Dmitry Kalinkin
Dmitry Kalinkin

💻
Kilian Lieret
Kilian Lieret

📖
Marcel Rieger
Marcel Rieger

💻
Florian Harz
Florian Harz

💻
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. ## Talks and other documentation/tutorial sources The [official documentation is here](https://boost-histogram.readthedocs.io/en/latest/index.html), and includes a [quickstart](https://boost-histogram.readthedocs.io/en/latest/usage/quickstart.html). - [2019-4-15 IRIS-HEP Topical meeting](https://indico.cern.ch/event/803122/) - [2019-10-17 PyHEP Histogram session](https://indico.cern.ch/event/833895/contributions/3577833/) - [repo with talks and workbook](https://github.com/henryiii/pres-bhandhist) - [2019-11-7 CHEP](https://indico.cern.ch/event/773049/contributions/3473265/) - [2020-07-07 SciPy](https://www.youtube.com/watch?v=ERraTfHkPd0&list=PLYx7XA2nY5GfY4WWJjG5cQZDc7DIUmn6Z&index=4) - [2020-07-17 PyHEP](https://indico.cern.ch/event/882824/contributions/3931299/) --- ## Acknowledgements This library was primarily developed by Henry Schreiner and Hans Dembinski. Support for this work was provided by the National Science Foundation cooperative agreement OAC-1836650 (IRIS-HEP) and OAC-1450377 (DIANA/HEP). Any opinions, findings, conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Science Foundation. [actions-badge]: https://github.com/scikit-hep/boost-histogram/workflows/Tests/badge.svg [actions-link]: https://github.com/scikit-hep/boost-histogram/actions [conda-badge]: https://img.shields.io/conda/vn/conda-forge/boost-histogram [conda-link]: https://github.com/conda-forge/boost-histogram-feedstock [github-discussions-badge]: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github [github-discussions-link]: https://github.com/scikit-hep/boost-histogram/discussions [gitter-badge]: https://badges.gitter.im/HSF/PyHEP-histogramming.svg [gitter-link]: https://gitter.im/HSF/PyHEP-histogramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge [pypi-link]: https://pypi.org/project/boost-histogram/ [pypi-platforms]: https://img.shields.io/pypi/pyversions/boost-histogram [pypi-version]: https://badge.fury.io/py/boost-histogram.svg [rtd-badge]: https://readthedocs.org/projects/boost-histogram/badge/?version=latest [rtd-link]: https://boost-histogram.readthedocs.io/en/latest/?badge=latest [sk-badge]: https://scikit-hep.org/assets/images/Scikit--HEP-Project-blue.svg [Boost::Histogram]: https://www.boost.org/doc/libs/release/libs/histogram/doc/html/index.html [Boost::Histogram source]: https://github.com/boostorg/histogram [Hist]: https://github.com/scikit-hep/hist [fastest libraries]: https://iscinumpy.gitlab.io/post/histogram-speeds-in-python/ [spec4-badge]: https://img.shields.io/badge/SPEC-4-green?labelColor=%23004811&color=%235CA038 [spec4-link]: https://scientific-python.org/specs/spec-0004/ scikit-hep-boost-histogram-5f6f3e3/docs/000077500000000000000000000000001516627434300202575ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/Makefile000066400000000000000000000011051516627434300217140ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) scikit-hep-boost-histogram-5f6f3e3/docs/_images/000077500000000000000000000000001516627434300216635ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/_images/BoostHistogramPythonLogo.png000066400000000000000000001100511516627434300273560ustar00rootroot00000000000000‰PNG  IHDR%J©ð9 pHYsÃÃÇo¨dtEXtSoftwarewww.inkscape.org›î< IDATxœìÝ{xTÕ¹?ðï»f&÷pM@Q!°ZAHHÈÔšªÕ‚/G´§­öòkµö(—ÖV=m-§o­U´7=õ´GÛÚJ=V‚R/­x! šÖ ’€ˆr„Ü3³×ûû#A“!ÉLfÖÞ{vò~žÇG™Ù{­×�0³ß½ÖzA Å×L"å{˜µZ†Ú;7»ŽB!„]Æ.;Zw¤�ƒA�3:±wÏS{ZÝŽK$†Ü@CT²t €"Ü¡Ñþ=ÔÜv;0!„Bˆ„]‰@î;ùåL|ˆf˜  €¿Ÿ£[¼`mòiÞxx;7ŒV$H’’ábö²eD|gß¹š}þËQý³7Ü J!„"ð�•wžfºÝ\s’hm+Œ òP{ÕÁ�†"†IR2_{ )µ@f?ï¶3øFÔÜ}7v82!„Bˆ¸�>ôX˸LW8Öpó æ¿øqËšýë ·-’$I‰×M»6�F«jteÀ.Ç÷säã¬Â_Á¦_¾ç\pB!„±�:oÔ8+=íbú€\Û;$¼À ¶®Þû´í}‰¸øÜ@$GËoèÂ#f¢ÿæ¿ÑYÒzú1bßplhÞÝøšó‘ !„BDY•�3árø}« ô)éõ<…€ËÓNÊùDúÉ™»^o{ß¡~Åd¤ÄËæ\{&iõêÈKÌôoØr×ÿaÎòBÒú§EŸFÀ*ÝÕu^ùÕA'ÃB!„8"ëü¼¥è·Nu9”v0nn±Fÿk·uºˈ¥b"RRѲ1¤Õèg¸éÎíœ;¶‚�ô©ÀÅÀbJK¯ÅœkÏt R!„Bˆ�TÀŸ[™½Rêe¸Ÿ�@&?ÌñÞ’½hÂÌØ‡ ;HRâQÊÏ¿0ùèwôG‹Ù×­ˆ fåmLêt€£*pñÒêYU²t%¦]ëÔP©B!F°ÑçO,Ì•¿��[N‹}†“øbÞ�³pÂ×ÜŽd$’¤Ä‹f/û"3.ë÷=RGWØÚ|g5§ùŠˆ®ÀE ,¡Ñj3æ,+²'X!„B «2¾¥x3JÜŽe™ þUNeþ#y‹òì_p/>$I‰×”]$╾Oºÿ²¿ëïl×5+—2ô§ìŽzwi^�’¥×+äwB!„&QNeÞM Xð8·ƒ‰ÓÅŒõ£/œ0Õí@F ¹õ”Š"þß5ð14ø^$5÷<ÅM'ࡨw2¸•f|s–&ªB!Äâã3s*óÿÐÁs÷�4ݲP�½h§܎d$ðØ/ÇWrð» |qºîgúV´º»>Ð5+?ËÀ¥úVà"Ì#­k1{ɕɄ*„Bˆ‘-·rR^NGçSè¾ßð(GÌke�‰ý$)ñŠÙKJøn옾՟š•«XGЬ‹zgÝK%KV¡ì?Æ%L!„Bˆì &ž tmpºÛ±àëYg²²�†m$)ñ‚™×eá�ÆÛ®ýÅ®Yyƒ—ˆªÍM—�åoÀì¥ �÷+„BˆaiÔ¢¼óIë—4ÅíX [’½0ÿ7X!÷Ïv�‹ê*Ðu@‹ë`ᦤŒš»W²R%ê¢Þ›H„ÕªtÙ½˜y]vm !„b„È]”w¥fú+€aY¹Š_ΩÉû¹Ûq G’”¤ºÒ%0è+q?Põ­xlº³�érn`õn•™¯¤@x+J—µC¼B!F¸ øsåýŠ™îàw;{ÑÕ9•¾îvÃ�Ì‹Ke3¯›@�ðVã=…‰`ó]k“î{Î’y¤éÑ¥ð"Ü¡Ñþ=ÔÜîïT!„BŒy‹òr;˜þà\·cqÐa+bMo_{`—Û� 2R’ºˆáßb @'1RÒÛ¦»_f¿žMD÷E½ãgàzBÆ‹(ûF|SÊ„B1,åÌ?&¿“éY Œòù}ßw;ˆáD’’TU²ôZ †~bBkJú·ñžÃzó]W1áû£ú)#˪CÉ’¥�7!„bÄsá1ð[/0Pêv,.ù\îEÇI•RC$)IEÅמBÀ­ �›Ìš’�l^ùû1Àš¨w2 t•,ûН™d¼_!„B¤¤ì &ž±¬—œäv,Fê@|_ðc9:“#�—8Úpçs;eڵ锩Ö8!±ø÷x·ú-£1À;[ñîÆ?áØÐn": @Z¯w§©/àØÐ6¼»ñ5ã} !„"e䞟ÀOÈw;ƒ: øZKÕ¾¯u½Þö\×mµ/çSó•ö´ùa zkšu ú›Bîz½íÏ·;üÈHIŠQ£Õ�%Üű£{â[î¾�•š २÷òˆèÿTÉÒ‡qêÕcmŒA!„.É]”·ˆž0ÆíX ÒD|isÕ¾û|xåóë/PñµÖ´bbº¾ß3Å8ìIR’JŠ—~‚�åIµaoRÒmÓ�Û9wl7èS�‹�Å”–^‹Òe¶Ç!„BÇä.Êÿ23= ÓíXL"Ð�Í«÷¯îçõÏ÷üç‰/îÏzž�žÆc³.Î;ÖÞGIJREѲ1¤ð ’ý™D,û“X·"‚š•·±Âi^ïû&O!樒¥+1íÚtGâB!„m²+'\ÇŒß`øíA²ßŸþaô‹ ÓsÌ>òg­é ú["ÑǪN¶7Ä‘A’’¡ÿ Àä¤rb¤¤·M+7qš*&ànôòDw°%4ZÕ`βħ£ !„ÂUÙ•®#ðO0 «mÑW<ýz¸³­Ï¥0½¹êý×x&úXfcs˜#‚$%© téLøŒ‘¶ì¨¾Ëú;ÛuÍÊ¥ ýiïD½;�4o@ÉÒë�òû&„BxHî¢ü{’a‰´Õÿ"uMÓûü™14è�Gµʳ#¶‘FnÝVvM�÷kÏé‘’ÞjîyŠ-šA@ô_Øtn¥Ù_@ɵÑ;Ä !„"å.šðmfÜìv6j;Üòþ–þÞ æ>ëDÝ£!~MÑ…~@@–=á�,’”¸j…¢ˆÿ·Fk’ZS2�º»>Ð5+?ÇÀ¥öy�0� ¶`ö’+Ý N!„ñè^C·¹‡�hÀº£×ˆ€&Ήz)=¾§@ôt¯a7­Í ’”¸©äà‚øL·Ã°EÍÊUÜ]Úx]Ô;£ˆè^š½ô/(ûÙU!„H1Ù‹ò¿@àÛÝŽÃnØ;Ð{Ä�”äpwòÁö÷=X’$)qËì%%Üd¼Ýˆ‹Ó·¢Õ¬ÜÉ5+Ïbð2�}Þ#üYþÌ^ºÐ�à„B-gaþ%ĸ#àé?-¾©Žš’å[?÷øŒžÿ>Üçþ×Ê ’”¸aæuÙDôãm»¹¦¤Œš»W²R%j£Þ›H„ÕªtÙ½˜y]¶Á !„¢[vå„sÑ}âs;'00ð½£#ê=wý®#Xsú*I‰ ’”¸@¥E~à$·ãpÔ¦;ø�žK„ŸÐ½Þ!f¾’ü‘ÇÝ M!„é²M˜I࿜æv,N!`àªYLÑ£(­ôÑýKŸó¨Ÿ½KÄÐIRâ´Ò%0³} ½ëعdÛ=�zóÊo³¦Ó¼Õç=â‰î%„BŒlÙ ò�!Fzs� 3°x€Q!ÅÑII3dVŽ� `lï7h³%ºF’'Íùú1Äôßn‡áºÚ»Ö32JÐw.§ } !„N«œ”¥|¨8ù œ½''»5ÿÔ~ßá£ÁïûçFJÌÑS½D$)q�²ß�ok'nlž˜ˆšÛèúè–¤D!„pÒ ¨„d ÔíP\£piÿoÐ?{ÿ‰Áÿì9þ²è#µRí6D6âHRâ$Eö/æN¥ê[B!„HY¹5ù7¸Øí8â°žÀXPW¬ƒ‡‚_@夣6?T�HCÔ�  Ž�¦ó�jƒ!I‰’” 7nož84½b%)B!’³(ï“ üÀí8bx�4>ÑRµo^sÕþ+Zªö}Ò:„' ö1)‡º¾ýâÌwðú‘?+‹6øýÖ�û+@¤Ãã±$)q;0E)õJÇK’!„ÂYç ¦?"µKÿvj‚æÇ÷½�½ ¿({aþ͹•.ËH‰BaШóF�#À“£$@ î)g¤ßq9”D\šµ(¯Ôí ¼Êéˆøyj¤D¦o !„&Yii_ Ô[$Îÿ“@›™ð6ºËþv—þeö0 P“:ÈŒ·€@»¼tCÓƒÓÿ`þ´9X»­Óí`¼F’'1“í÷Þ䥑’>$)B!’CºÒí ºQˆWÓà¼Ðüè;ïí|Õh{B³×©¹þ~Ô |Ëí@¼F’’áFFJ„Bˆ)gaÞ™Nv9ŒCD¸�Ãê7-kßÛ—h#>_çs+`ðŒÍ úf΢¼š–Õûÿäv,^"kJåÀBwo‘¤D!„0…”›£$@÷"â;±yõ¾[’IHàƒ¿~𵦂s�éþÜENs;/‘¤ÄYößx+ÏŽ”!„"A¹•“ò¾Ø¥îßgЂ–ª½_K6éM?kª-d1óšì…f¹ˆWHR2ÜtyjM‰Œ”!„Ft]Àù  u~Ÿ¯´µjïS64þóm:j ÿ}ÔÂñenâ’”8‰e¤d’”!„ Ò  �ï•TgçÙüõ½&;ZÏéP/h³£m�פžÉ©–Û�¤:IJ†OUß’}J„Bˆdå-ÊË%àlG;%ì²`-8üäávu±ç©=­ÖØÕ¾ƒrõDNå„s;�T&I‰“”#%~%%$I‰B‘¤M d8Øe'“ZÐ^up§ÝÓp©`•ðŸræ}Õí@R•$%NbªoQØCI‰¬)B!’Fpt�;ßo}lÏ+NôÕÜ’µÀ!'úr€Ÿ‰îË©Ì_‰rM.ÈpÓ契©¾%„B$‰:ÇÁþ64gìû©c½­kê ÂcŽõçŒ%9›óÿ€Š'G·Rž$%Žb èKFJ„Bˆ$ä\0îã�sª?µ«`9Õhà!'ûsá39£ZŸsá˜1n‡’*$)n¼5}«7IJ„Bˆ!"K9¸AýãpÕž Îõ×­5}ßÓÞát¿¶cœ±ü/fÎw¼Û¡¤IJÄNŒ”xj¡»Œ”!„ÉÐDóœê‹™nvª¯>VÁb¦ß¸Ò·íhºòû^È]”w’Û‘¸M’'9±ÐÝ[û”HR"„B$�€¹uõjëš=w¨¯£°Æoêr«;PÀLÏg/š0ÓíXÜäw;a˜òÔô-Ûc­-*ãËÑ®%<>nxúº†·úBŒ<É|îÍ|qçAÓñûä^tÜxŽt}̑ΘÿâH?h}bß{9•ù�¸ÌÍ8l4�˜ŸÎ9|EËãïÿËí`Ü I‰“ÈöÛð™¾Õ¥S“ûFÛÑvæHq}Â-�ïyÕ�žÕRµÿ¹œÊüWÌp;Û0ŽW *TN:U#ë¡£¬)q�ÈïAÈŽîø|Œ€K|³Ö¼µ¶¬`[m¨àæWBÁ‰n'„Â>šq¢#1žq¤Ÿ80èv·cpÀìD~ævN“¤ÄYR}«–¤ÄD4•@7ZÀöºò‚Ÿ5Ì=Þ±úõB!œCp&)!æ:'ú‰Gë±{ÿÄ@“ÛqØ�¯Ê­Ì[èvN’¤d¸Q]JJ¤ú–ͲÀ´<¬ÿ¬/VºŒBãYänjœè'.÷!L„;Üà Z‰ùÓÒÝŽÃ)’”8ʉ’À¯&%Â>™ñXm¨ð®g+*d™B Sè#ÜÓà@?qkIO¿À^·ãp@aŽÿЕnáIJ†›vÏ&%2Rb/"ðÒ1í;þ¾åŒiùn#„"9Ï�˜ Ë�®ÞBªUd[µ«�w¹†#×`„Ü#IRâ$v`Gw_§—’’ÞFÄ_8·ð Õe=¿å´&¹‹BˆÄN‹Œq¨«=õ3$éàŸØïvvaà·|ŒcsåŸáv®�GßA è„b8!h§’’}õ3d­-9¿ð¶Ûq˜ÆüàÈ·®Þû{ïfìç§¥Ù~ÛíoóNR¢Á}¯Ç V¤jüOáa­q”B§êžg«­V?ù»€|ô‡»8ðY~eîÝÀûþÂæŠuëô+§O}䵈ö�Bc|PãYé<€ƒ`š`€™pêÁÁ)7(„ÆC#%€òDRrxõ¾m9•ùwøŽÛ± †�]ý½NDï0 M¬þ!„‹áËì{œþÉå:  å�x<Í-«÷]ãÈ=Žu¢SšÛûrveþO ¸ÞíXúG…ý½ª5¦öÚÍîFäõœÊü`žéôÎpÏJ$)n|YJJ˜û¬moÞ�º ÝŽ�”D+ªnz²6¼Ž€ŸÛÑ>Þy&„ÃÀ¨…ù'ZDå.ø$€ Ð�ÉÝ_æÝ‹ pä¿»ÿXtWßrh·¯£^§¸ÖÈèïç�F*V–c)pØ;I ÷¹“R6)ar?)€â��¿¨ /Paºm2M·)„¢Ê=?ÿtVôy€/Ò@þGË+ãû $î™ÊËL­ô\R‚µÛ:­ùã*}~ßǹN4 üôÉÉœ?îx@Íø‘Þà�aE’’á&-Û;I‰&{çû)›”(æ”HJ€À72h½évJ’!ôðbøN~wò¨Þ¯q8-›tWÚ‘?‡ýÐÝçÖY3«_"t¯pH}Ù” 1Çõ©“¨ôØþA$¸uÎä®ÄÎîö~váΞi§âˆŠ‚ŒœÜÖ+XÎ@A2(™?\_èÔwf*àÇ:xêgÚ¾öÀ®ìù ɇ猊y‚ƒˆÔ²¬…ùϵ­Ù÷ÄØÅcG‡;}sÚÀgð+ÎEçIJßJRÒz')!îëñÍ„m.ÅCªŒ”À¬�Mêʃ›À˜c²]§äNý̉ٔ™s2“>N3Nè™fÖ3Ռ҈8Œjep �÷k ‰™š´EM¥5Û¹ú?à¢ú™³­Œì�ûOê¹vǘÐýîG׎‰[ÀÔJàý jTŒ¦VøšÊ7n;„=ÛéÍ Ö'öÕe_0ñth½š€·ãù§)šœÊüWÂ8ÀØA�&™Š$)q3�lÎKvåz')ñN�5%}ÑŸ6�”¸~Ê€ÚZ>¥Hk:Ds(b �¡¸¿Ç‚Œ�RÛ#3°»ÊϨ ›ØL /Ìœ¼a8>Åe@½šZ¬É:LsÌb P�Ǻv=ÏKÝ3L-°P 62Pã…kÇ€ª/+\Âçþ„L@Ô³z­gölÏèl÷ Ç}O—²Ã¸)(ÒÙ>Ü×ãÆ%kQÞ$ÕAp¦Ù–û/)k'MÖtžKJ õ±=¯ä\41„ˆþ?§¹O/„îýÈbòi-I‰6ë{?Ð96e¿÷•J­¤Dkk½"³½™•+7 Ó§§uå´Î'¨ÏÔƒÏ#Ïpî^Ðý„Œ/€±í;>¨+ >Âjoû¿Y[?Ú Êk¦OOëÊn]@DŸ©ÎtžáÙ Á^×î`]Yðiª¨½íÑT¹vµåSO«gý €g¹‹€ž¹uO›ÛA¸-wᄹÌü€|ã�u?8jb)š Ê¡ÞŒkytÏ^ÌŸvvnàÐ}̸Âíx†‚�ÖÙï×¹‡$)q’¢èíÍK陋’¾±vµ¦lRÂ:uÖ”€¶ÒßT~³ËHÙÝÑõ¥SgÀ§¯îBÛeïà¶5c@¸À¥œ™õ‹ºPÁ*V¾û‹×¿õ²S$këÜÂS-K_¦¶Ë4.öÆŒíuíšSáÚÕ•¿ ÖÿÙw+U´Ú_Ñ%µåœ?q“^ ô]?dʇkJhðuHFûMwª/Û¬ÝÖÙ |!{QÞVbº@Àí�âADÅ*$µÎË+Ì'%%W2¥ëà�¬Eõ/Þ7Þ‡˜uŸélþ”½©à)ÉÌ t„ÛÍ&% ô†ÑP*(× º°}Bc,¹}™´þr](ø"˜TTÝô¤»#å¥* IDAT! lKÙ”¹ êF­y!‘Û—®Ïµ{À�Š66>ådõ¡Â;™y™“}Š˜†}ÙÒAÍŸ–uèaØ”�½ÖÿEW°´ó ‡z²]ëêýwŒº`â‹–Öu�§6þ×íœbþ&�³.eàÿáA²ü{¨déf”.[�ÙKJŒ÷å5N ´æ;÷äd$Iµ‘’H[‹é)‡glzk—á6ûØ:gra](¸†Aë X„Ô›¦:ˆþV ¾\_>e¶ÛÁôöʼiSëC�+R/ƒP‰Ô»vgx²®¬à¥º¹SŠ�è°®¼ð IHRЈNJ²ý‡¯ðq{{¡0;ú}2*†ÏìšÃ�íÙ˜–)FªîØ|aWëì½w; §OJˆô’^ô(!æïÑf*Yº]•.»³—WbúŠAJŸ [$% Þ6““j7Zò¥ØH ”:Õh{„çíšrñæüiéuåÁïjå{Àùvôas™Uu]Yðî ¡i®–‘|sþ´ôºPð{–e½Â nÆ¢yÐjS](¸ÒÎkW;§p˜oµ«}‘ÁIÉ•x¹ýÑ‘ê[N~ß§çŽÊ›ê`¶;¸êà¡–ª}—ñUÚÝŽ§_Œ_cÅÈyØl6))]v@eƒdæ+‰ôjÊ8x€J–Vaö’+1çëÞÛ˜'U­šî�¤„Ú‹Ö‹yÐr�Nãî‘cˆù“íQ;wê¼ÖV=?„·6gô�pm:G¶ÔÎ)peGàÚò©§µ°¶ø¼ví€%°j¶”�/€Iv÷CÔS}K;¼vG«SíÏ!Í«÷ßçóÑ©žv;–(û3ˆïv;'MJc 0è›ü tál6€…Dt/éÀ.*Yòig´ÂÙ~Ìíòá¶ÜN¼ãÓoQWççà¥û9CÌ$%%Wf1øÿÅ<®æŽýÐÀ¨øbZÇž®´®dàB“8ëÈ4¯…Ð�_¢di­�ÖUØrw�‘ø�¢ØÙei©.zã§NJººÜOJêæL›XÀä·aͬMÛ�TöØ\Rh9pàa† ÉLøq}Yp׬êÆL¶»¹¤$ÐzðÀ_à…Åì ã[êBÁ]EŸp }ÞÈo?áUÒxˆ”®�hµÛÏVs;Y‡Æ�Ïl9qí6×ÿ®{ÛÈ�¾Å–?àÈŒ*B设ev¯Ô#jýïá5ïW8+{Qþ§‰q€¹¦ÚÎÖ„³;ý8·Ã�™TϯÍʃ™á?fóâžÙwÀT_^bh¤$ã ã‡tʺßuxFÏXŠâkgÁç[HÌ•æ ÿ©eGªy•€èû(Yº�UzêîoaÕ*+Ùÿ‹aÀ[)OtŠ–“‘²IÉøqî&%[Θ–�.« �.&¢…™¾aª1ààÝ`³ ð¡#B6€,‡ú~];§`kñ¦&c;ìúý~ÆBSíÅàÚµpï–²à+³«ë‡zbCÅôœp{Û…Iöo1øº¢ M+½¶Á� ¤Çyx‰?¼8 ÀpÝ¿+AÐÄ8Ä€E„Ç™©ŒN€ÚX¡ƒ Ûj#pu‚ýz“•FìáÉGº;ÿ˜Óüîôкzßßü-§2ïL]ß3Ó'©´EŒs;ü( ÷Y;FSà›Ítß7gN¬˜µuOk2}x‘‰¤„´$öa1ÔÞS ž�aš×4&,Ǯ㿃T-çÖ3Ù<Ôê©/YáŽC8~ëœÉ®\ß°? T—õ[Þ䉘®,ªÞþ¦‰¶êÊ ®ó×L´Õ�ƒž"ÆÓPü*ùõ3_Üy°÷�ÃÖ8Ÿ¥ŽaöúôžQ‡‰6Ä“ Â_6—LšYZ³»-ÙÆêË ¯fæ+MÖ�žè)"ÝëÚA«b&:àù°çÚe)ª—ç?kÞú]CúÌ··ÍCrk©À—Š76=˜D®™µ±1îÊGu¡`;€ŒDúÉç› £E‰ñ)_€µ_úHI`rºþ–¹µŒÔRµÿ9Ï�®œ´HŒ/8!‘¶:¸nL;~úAæQ‰ ¥ÈÌúó³~rݺˆ�Ð=#ù¤¤tÙ§Ál¶bVÿÓ¼Îé^HOG/´êjMÙ'ìóZRÒ7^«+UŽdYÖ6(æ ÷çíXËȷΪn|ÈDK5åS>¦Ÿ™h+ÊëÄøIÖxßïcÝ$×5uØÝóÏÿóðbøNÜYxž/gà“�ÑT ãû®O¦�-¡É§0ó†Âêí5€~hÉüýô††®ÁìçÚÝÿðbøNÜQøiR¼Œ³ Çvb–ößàÆ¡œÄ„9IÍŒ!ÜT´¡Ñ“ ‰ðÖ:àÄ"ú¨$°£ßšä¥Òä¶9Tµ§À÷Ü”U9¾” .&à\3ÿ}µÕ¡ðòåv>ø—Y—ûº7 ííüqí;~ À®V))餄ÀÉ�’ ¦ï4¯P|í,Rê�>zjÔáOÙR²óvRVòstƒo)ÞØôŸfÚÕkú Èè 0@·Z2ë†z0—®‚lÀµ¡à§¸&G›˜—×—N}pÖæ·^Mèt€êá»f÷ ó�ÃÖø›Kkj‰6Òsíðx](x.º¯]�© úfÝœi¿/Ú´­!Þ3cNpuoœÐx 6$Þ€±°òÈ–‡H�ÆÃZÓÛiÙáÆ#/ÚÒÙ€8 ðcFÔÓûAp[Õû›lpcþâüœöNžV' �Áǃ)ÐY€jƒÒ»ìÖL›3"ÖKÖ8Ü û´“þÔéZ à´>�_­-î(ÞÐøc;ÿ'6—N¦€•]²aÇ¿ìì'É%%¥KOãýÚi'Íêˆt=s ñì×·¨Lä亲‚+Èì~‡µâE³×7=4k´hcãSBÓf¥Cÿ‰º§u™`eÝ à‚xOàÞ¢†ˆ?ïN´„°[:�løJc`MKÕþË�üùÈBͤ�Þªd\測8pØÑN=bߪ}-žíùgP½çýžüÒëͯ�vÒüŽH×Nï}1~X[Ü[¼¡ñ¿ÍFÛ­®¬p!ˆïŽ„Ã3ìh¨’ºIVËád=º#¢ks§ð¦{b¬tÉPìó”žWd0!Ù\R` SíhÖLg™NHŽ8ù¥×›ß˜ÜxVk”±°¶|êi±ìksII€ˆþËXÀauVwBb^ùÆm‡ßœ¼½À#¦Ú$`Ñ–²)C©f“p‘ Øœè¹BÄËGÊ®}Jú�u¡žggÀ/S¸lpòK¯7·©ð¹ý|?1~]_üŠÉþž­¨ðׇ‚·ƒx5ϘXiBâ#%§^=– Ÿ7Ë÷HáR²}0ÈæÎ[#%šž;"Y ºct†þ~pÝŽ“ ûïL¦¦Ci"|~öÆí¶Þ<^º VÃô¬Ï‡sÚN€¡Q Åú[èÞ6n~ÿÁËL1Ñ? ¦Ïͬ~ËÖé=×î³áœ¶ç„L´©”ï[.Žóðœ„ûñÅ]µJˆ„Y°ÞVP÷}øs;âúÜ%æv"êÿXê¿¢Am!X7|Ø¡•ÀñOye²˜0¤Q�Vö�Ø}hì6oý®v.« l#Pï5wŠ÷Ö–ÉÄˆÉæ’ÂÉþ¶¦‡˜h†¥îN¶MSOJÒÒ®‚[•ˆûVœÈ”‘’ÞJJ(*¹Ì KŠbýŠ¿T´~»-%:‰éjSm1áÖ¢ �«Mµ7˜é ][æ?£,¼Š$nt�``áæ’ÂÉ¥5ÛwÆ{�¯6ø—õæ¢êíkÌ57°é ]µåŸ!¦WaâóŸ¹ò•9SO8uÓ[oz@õI”.f‹+ŒØRµÂmUû·¸Ê©þZªÞk÷º,»Œ>ôX clï׺TW$Mëf8”uè0dúd\`llúÏÚò‚7ˆé^i=o)bÜ[_V0iVuS£ìµeñý�0žLt]¤KJ*Vø©ùà× Ç¿£6ÝKÙªM})Û[½•”€ûÆ«%)1ˆAú�ÑéØjGãõ¥Sg0t™¡æ^Ëëû�¡¶â2ûåÆõ¡Â›lbwvŸ/À—ˆk1â+¡àL (5Ð/úgö8õ#3mŧxCSSmyðb˜èׯÀ-ƒD×uﲞÐ."º¦±¢àîžJcB hô…¦ZŠ@¬XS¦R:4(SñÐË,3!‡vMë: �Æö}…€ÑQ‡ùÀÜ÷ïQ€¨ïCf¤á臙è[n:×üˆšUïƒVÏã✎| -Þx@�5WíÞ?„ÿµ¥xCÓïjË šˆéãz^&&ZQ*,hS]_JYõ7çOKo=hýŒÿÀGË.4|ÚHÑSKJZ\PBµ™� ¨ß~¯Tm²²’Ç’’¨xuš$%æ˜n=ÔŽ%µ¡àÍVdÜ}ÉTcŠÆ>ëbSËɘøj7öEðgfÞno»}à'ŒºlÇ•”Dˆ.&6óWUiw®]¦/íîŽH×7Œ�yp ¾1’ àN|]É ‡ÛÕ-–'x¾¾(û‚‰g+m]Á  Ëâî{îÌçžb,„¿`Ùé…·qFÙÏŠ|CKÉp@'8›¾#»2ÿ–å_ÑñÄ»;líÙ£Š74­«-/(!�? {ªUþbJëK§þ{<£õ¡‚òÖÖ}NíóÑ}EëwÔšŽ; ÝÌÓRÓ� QÔþ²@º‡·’’èq#‘Ÿ£y“øyÀ ¾§´«!dª‚ÕÓÅšÖjkH¦¯khð[#�1J_ ãÚlP1›ºvOÎÜÔø¼¡¶†ää—^oñï 57gËÓbîÍHnÔ�ÁKkË ¾˜LbxÉY˜INå„WIë§t9ÜO$$@Àý>ë�œÊü¡ÂÈfÞÃNñ†¦¦ƒYgø!Ðk cûô–ºòà= s ŽéïÜ­s N® `ÐK8*!Á«�ŒÌoÙzB†ž”//…Ù2–C]}+½Í#%ö?0ñVRM$)± ðd]¨ð×/Ï=>©ê)õ3'f˜m$.R&+P ‘þ£©¦,¦9±j¨˜žÃ@‘‰5kW¯�ÖÊÔµSÔ¥c^;ÿH²"¦ÿ© þzëÜÂS€-RFþ¢üi£å?Â*€.5-Là4ßÉÉÍ!ó‚q’öã“ëÖEŠ66Þ¤4ÎÐ{ý]ŒkŠvÕ‡‚OוÞZ[VðíÚPá]u¡àf­é_.ÇÑ÷úÛ"a}nσ¹”2äÌT)ý ×ï|£HË´or|7ZðUY:P^7÷øó‹Öïz'‘(=«„Ml¼JØY´á­!U­2m憵õ¡à{ú}Ò4$¤gtÁyWGk)�L<lš]½c½�vV\½½¦>Ü `B²mñlO vL@óê°¢{ðÑÂÏ„ºø*­qU}(x°ØÁ wñ^Öôˆ÷¡{íJ3:‰pˆ‰»|ìkS¤-`qg;Y‡r²Æt¦âºˆmRåÄ :Yÿ/½ÎB¸«\ißó£+'žÕ³cºˆ2sSãó[OŸ¬Ì�é[™xá•yÓ>uêËÛÞêÉšø¿Ç ô&Ô p=áf,Nº1¦�Å>F�bâ¯)Ÿ�wBÏâóü[Ò�1b^»é›šÞ«/>ÄŒ/$Ý_·±Æ¸ˆÝ•æ{92垘 ¡ác­€tønoC](tïc× à:hcûzþ½›û؇×"]�¯¦Ê^#Õä…+»˜ÿÀ/ÄRÖ³9M,kytÏ^·ãIE3_ÜyÀg¶–Où_Íêç ‡pú~búj¢$�0¤›y¥Ô5€sU$½Á+7³lÇ^¯}{°¹}£(ú.@¦o9)hYÖ3ñ®ƒè�H�d$­ë�´“,�7Œ´C4)æ!`#׎�”¸v 6tíóÚuwèû.€T¡ÈFw¡„ž)’% |Àå |ÀO™ð¿ÐØè÷§¿_ ®©ŸSx¶«�PÁóóÏ`æ?ð{ê‹r„aÐDôï±"¹Í½‡»™v¬mSáÄø1€x÷¦Éq]}¨ðÆg+*Rr Oü?ô¹Ë3t¥�±Ä�£GJ«ºñ»é"ëâ<-“Á7�mßñB}ÙS›¦Ôi]¢ñ6Æ7BTAM¯Tm’}J—Â?ÇH„ŠJk¶²£í7çOKooîÊŠhß(²(��eMy`�ODyÌ<••‚1€Ïp÷å-­›|'î3ˆòMüª±ò§Äð¼Vz»ÒÉÿêÅÞÜO1òÙÀo93§ÄµcÒÛ,âÝRÜ#oÜþ˺Pðè;§Úk>îh+ª™;õ¢’õoms;˜ÞF§�ö»Üèˆ/¼„c$!ñÝ”{Ñq4?úÎûnÇ’êJ6ìø€OÖ•M¹¤nψ|9“ª«- ~¥¸ºq•Í!Æ-ˆþÃÎ@†„ÐwG÷€WÖ”ØÎ[ŸµÄ챈mѳÏD'ºGOT77X�Go²b|»¾|Ê#³6ìØßœTõ®#[)±Ã/Ã÷NôÆ_ µÃ±`3Á̵󩔸v`ß;Ñž Òh]› )KŽpº‰Î]Á˜ác½¾¾|Êyñÿݳ_{s»é®š±`§ÂÀç%!ñœ1ˆtÞ åÊÖ¦ª¢ê�>[QQ5¶}Ç—übpE„?ו–̪ÞþŸdâ‹0IñÝÌ—\{.€™ö†2Ñ%�Ù#kdóľtT¼^ù9º¤h}c5˜ÎFŒä%~fsóº^ÌÜ3¹¿> wq¼óqc‰ý�‡9b¢#f•×NiS×nH7ÂóÖïj�Dh!W«·�Ǭþ^;§ÀH™h,_gJÎ5O1ý0:!11Z)ìÇ ¯ rRÜ£¨âÃòÁ÷Q{Û4߀îj‚ƒ!_¿µ<ø§Í%î_븒‚Jµ¹}Q›'zfA”|ö=™--u§o¥Š¢êío`ÇÚ®skçN�û0€fn¬I§ÄÍ�ÊùÿÐó"# “•׎(6ÔTìk¥´fû!jo;�€Ç Åà–1¤èÑÍ%Ë3Øf¨2üÃf¤¤xþ1ó½ãC6&‘ä«û�@³¶îi-ÚÐt[$¢Oã0:;ž‹ýþôg7—L‰k}Ÿ]bßÌ-9À§íe(j¤$µH;ÉkŸ·2R’€Yÿàiã k}U<‡±2“”�¢!MÙ±�JwðÆšŒ\;¥SãÚùtÀ¹„®³¶îi�¹±ñ"b|€©Ÿ£ üþ®Û ¶—ð4ŒpgdØ$%¾²÷¨ˆ$&Þ£ÀŸq;/+­ÙñnQu㫺ƒN–ùýªzëÜÂS9ÆV1“åÇÒxŽs”ŽšÄì÷Ìš™¾ÕGUß’‘’xÙ±:ÿÖ³[ûà´™›?ÖdòÉpÂ(‰ýÿO;ÀáØG±™kÇ©qí4k3ÃýsŠÁ §‚gU7Þ¬sàéé\ô…º9Ó¦j,‰9_JL LVhþ¸Q8èûÅè±/É�� Td¸‡×�ºé­·‹6nÿZ$BÓÜ øpñx­ù¹º¹Á2ÃûÐà7óEËÆ0“©�ªÌ‰)a)éáíÏ[öËÏ1NÅÞz‰�­†›ÍFvæqg¦ S¾‘v’e)#q0xgǹvŠtJ\;ö™¹v`ļv±Ì®n¬/ÚØx:˜? ¢G‘àè‹‹)ËÔ÷mÂ#%¢Q†bp•FàL�£GG¤¾ŠÇde�n÷nA‹SZ³}gÑÆÆ«t €ß£ÿc¡ñd}¨ Üáðb,ÌTüU)XK=ú »Œ”ôðÖg-‘|;$�À�d¶…¦³ümÐc½ýW0D±÷õp„‚¡kjŠ}ÙI—Y¥Äµc‹òÉLõ­&�@QuÓ“ž|x1|'î,,TÄ'Cc&�‘€³ 4Š £ÁPdqß `9è»Qp.†RB?Ašùbß6ÑTÂ'ú1Ú@ÿ)€Î<òóá¿Ék_’ÈÒåžq;Žá¤xãö7\^_6å&&u€ÿ‡¾GÆ0è™ú²©‹fU¿õ§âøCvñbmÇÕN2$̽7G÷{å ;Ùý‘è­Ï[Mܧœ¨¬ ¥i�6œŽ3#޹¤ldƒøú²Ÿ"L1�c*ö�µï2Q„�H§ÄµaŠ‘vØ\RrÄ¥«`Ûßð¦‰ö*¦ç„;�F$c HA|3N&ÂyN0Ñ6Ì=~Üôõ»$ÙTâ¥=5�I²ï”À̳ÑëW¦py˜¢“Ýa¸šU½£ÀU5åSîò±ºÀgñQr’ͤ«jCÁ ‹76š_ÇÚ��“’Æã.8åv{Ðý„½7�bk^Üã­ÏÚ¨=0‘æ•ä25XaÞJé†/aj¬C4è_ÊįaVò�$�Y—šĤ8žö3è_Iw€¡RâÚÌ\; žQ&wM_×РÀ;¬hkyA3nè¬$»!+˜àù$ÛI|M‰ÂpHJˆEG•v+‘­'ºÂp׳ãus¦Ý²nát/ñÈ"`u]hÊ‚¢�;žµ;Ž“bNµ2À½E­)1°³#l¯Žî±Ï\â>!ËHÉ�×5}P v±7킘 ¨Ó-ýjXùQ[[^PP¼¡©ÉDc‰£9&Z ‡©>Ö1ÚyUELÌâã6—N.­ÙžôZŒ¤Í1ñ©ÃJ¿š|+î €±¡i#çÔ—¿Æ“iOûŒLkL|¤„½?Rš?.7ŒŽNH’ýue •ˆ~ª˜þF{“lÎó˜"yZÑ—Àô5Û:!JÁeÃSѦm .Ûš|Š‚ït�œdê±-e…gͮ޾ÙÎþûÿv,Y6àÔ]XDÄ}æ´û<³¦D F  Éæ’’pÀô<÷˜¥f§ojz¯.Ü `R²�¨À=ɶ“¨WBÁ‰pŠ�¦¶Å“ Ì~éíÝu¡à{ˆ½ÓnL>?WøE²í$ªaNÁ1aÆÇ 4õ®û‰iòº““ÆÕ†‚Ÿ"àI´“ôSafn#Jøã´ ÙþÝñg¤!b ˜�$‘˜¼ë×êôC�ïÙžDxÃÍvÕÙ ó‘‰õPG!Ös‰¾foÜùO9QÖõ>§ˆ×n M>³ç=[ôûƒVÄË�̱¶ ³W«oÉHIQ¿eR}kH(°X™NÈãzÂJÄë˜é³ú».&%ü€’Þ—�€! kÓ:¨½OÝ×夤Ká34Ä�Ø�ìT¥”¢2'ž”@x*LÔšø©ðþüý®®T÷¯&hmÉÐñ¿–�dÎw¼/à››Týõ z½uõÞ£ª-æ^tÜx¶º’�28$-xU»Û¢_÷søV‹Ò–0^¾—I’·ôŒœ\Q_:õvøõMŠ}OÖ–œa׃¤£Ð%ËŽeæKíèÌ ¨Í=3R"IIo$µ“ácu‰ ÍÆ5AƒÖQ÷°nrgm�[pòÌõM¯%ÝV"Ý}ÎÈï SüI ëu 2±!ØÙõ¥Á“fmn|Ý@[C¦@Ÿ3ré˜×h&eh¦ç(‰_*­(éé˜ hM8ÆIÉöï¶L+-Ц>ZVchêÖK-UûŸì_À7Œ‡“ëæh ú§¾ý3Pë6 IDAT:[]Óíèo0t;¢_?ôø¡ƒ9•yèBã�²$%n›µù­W\ºuná©–æ+_ Wžº±q�é~Žº™Wà¯Ãìuó¢H³Ož°wóÖ-¾Žþ9zfÄËu[OŸ<À564WÉÚ4‹«�̼õ�kú†�v†¬6üº7ÜKVÄJWq—«ÔËÔµSì£åÚ²ºPð\J 4ætÿÚI�z_Rç³6ñý›ðH €Â7çO‹9�3•é@$t!5u+Áoê)l�JX«”Úè’ÙL!�~HR’"f®ßþJñƦÿÔím-v´ß7)™vm:_µ£#£XõýB÷y¦ú–Œ”ô]EÍ3¥�Ýg…}w˜`¼aBÌÅÚ@÷ºb¼`¢K¾¸un¡£%nPüÄHc„¿Í~a[Ü7¢³_z{7Œí<Î_®/�:ÃL[qö(6tíX;”kçŒ)HêËZ1%�°2(™|­Âó’�ÁM�Z·õW8É©[)WŒ�ÉHk Þ³©iIJR̬­{’yð1 ¾7ócÔça`‘�ý¢Ÿ°{¥ú–í¼•”õs”‘’xÔ•\OÀv´Íš‡RYãACÝ´æû^ld}B\êCÁ候’Ĭùþ!ŸCæ®+ë^îgÔÛ.[CÁo`dÓNEòµKu:·%µ#:w%'÷“¨2ùÜS8jÿ^VïQ‘d;ö$Ã2ΕRI ˆÆÙÔ²W<‹$õùAÓµn2$$kJ౤$ŠLË©¾¼ðj0ÝbSóÚO4ønî½´úÂdà}C}—lgðg†ÚT]hÊ'Üj¨¹mEÕM«‡z’îü€ƒF" š·5Tx‡‘¶b¨/›z¦~ÿÞ˜¹¡1å¦Ä$«K§�Oæ|‚ 'ï&s>3]±¹dRV²q¸eÕ*X Ôæ6MTГ“‹Ê#ù‘&¼�Ì8ãýþô|/ÙXÜBÄ÷w^7 LÝ  ´Asg¼í1QÌèLê ‚®I0äiî­+´(ÀxƒQáþ?#�;°À–N£D‹aëÃ/òa©gž³{w¤Äf»É�Þo&…§á)l�3¹Ù­þY©Jf¾ö&¶ÿ=Ô(ͺƒÃ¾¯£g£²¤1ÝZW^X �]RZS“ôãÞ¶–O™¯™`d#.þu0cÊo�Æ„ÎOS‘Ÿ†uàjÀÌÚD¸½>,8�9eé'×­‹˜hóˆÚPáêNæ²Í´Hÿ|}Êöß¡ÚLk©âáÅðag²(è@²q°æ·“اäˆê˃›fmhòH`*¨}bï¯üÚÉ>[Ûó Ì€ˆ¯¿Õûþ îÑm;åvä–ãØ|7!R§sÄèNJf/ŸÖç»ËDíÎf¶—v€Íq¦ôî2GÓÜwÄ+•“B�N~;‹Tön %ëÏC=iæ‹;Ö‡‚7›Zø `þšß ¼.TxmÑÆí/&Û\CÅôœH{û5óµ0³¯€™—'só?}ý®µe·‘±‘!¾>¶mÇÜÚò©×ox+éÅô¯�vRng$ü†ÑkÇ ^vé*X±õŽÍ%“²ü;3~�$GJ´ÒIoÌç#ßÛ:ù‡Ë~f7³¾ˆwb·—·’’èbþ~YSâýhzCCBÓFdN¹klÛŽÏ‚Pl0¤"€Ÿ¯ Ÿëû#VøÉÒš£7ìÌk§�”Ûiu]no» À c€‹ª›žL¶Ë§ßà߈©¡˜X¿PW|œ5ßßî‹<9oý®!-ÒÝš6*�¬Ë:"]߃ákG„Š64>m²M·4LŸžÖ™Ý>“ŸGŒ«NúZé.•ØÐ[/>ÕÙ¨u€‘üƒ0?À7…ÛÛ–Ö—<Κ֓�·iö5ûˆ#´OD!Ã�@&´# ËGiÚ—A�~   Ú�ÊíÐåûÿìÝyœœU•ðñß¹U½eëÂîDp‹$�®„ˆ€DÅDGˆâ:¸ ŽJÒe\F_§�‡!éPq\pˈ ê0Š !é„-®! ˆl t’NÒKUÝóþQÝIWuwz{žzêvŸïçHª«îsjé§îyîr èQuÙ”Ög�¬¨ëAê»�œ0¥[|MèFge�GmV´®È:™Ù#^z(ôd{P²Y�-õΧ�ªŸ’wRã<µŠÖ9G½Âñ(Ç€‹è³²þà<'ÔDxæÝ¾°mûÃãmdîúGž¾gñ©;€9ÄP¯*oAx‹zAðx…¾ªéE”î¾Ï²<¥°·àð â CÍy‡ï­º®ª…Z"RûWÀ#hïìä¾™<­Ô®zx®Oß'Eý‘1×)N`ó"T†'.ì�ÿ ¦¤éìy9¸¶0,?õEý{Ð(O¼B;-_° '¹œH²âøûñN¥Y°qç=÷¾ðÔeª£_—2BU g‰ÈY¨à(ôX<þÐÊU¤Ð“‰±Ûº_½.]ض#²/Ɇõm¹gñ©Ë�ÏGÕf‰jD^$P¨=‘àk—‚KÚ¶�ùµSq[UbI–ó‡_§ } £‹Î®½½åˆ_+U�ò3p'Ñ%%ã6dÝ�A‹Âß{“éÿ¯A "ùø#ÿß„CýÈŠúšð9\eß-íÌVîZ„bñ^áÃηf”TùxÃú‘,7ž׎ÿFYE[*§È›lÜyOÔ 7lØq#p}ÔíV�œ8]zƆ÷%HûS>ßs]T�©r[Tm�×È“Qbq¤Ÿ ¸ÏI‹ ˆèýI‡`ÊÃUSZ <Êî[ãßeB0 /”ärbѯ7ܽ#ÒúóïÞÑLtE+IÑ·-ذýÇq`þ†Ë�¯ÅÕ~‚²o�¿~gEìT¡ò^ýe£]7u$�©ì#¬#4nC':hb1â¤c”÷1áqêÆ8¹Ø„Æóüܘ:R»°Î»¾$¹´é[e#ðýlöèË¢®!àÿôÌï”1l/\Á*º´á®�£Þ�l4|{ÝÉïdbU9? ÂÅ wíøvÒ�T¸«ï~h}” ¾hý#�Nµ"F.%‡ÖxhÉÏŽj øÅkD†{ü ‰�}ÃHžî¨{r‚mn†âÐð®ÞûÒ‘ e¤Ä¶>¢pŠ`NnÎæŽZu�>o\K~þ†—«ð Â/Ž÷î‚ ;¿_Žƒ½dݺ\Ã];Þ|’À_;V¯çÌ¿kGÅL#ªLzëü ;>GËR¹ÏÛâh{´Æ2âq¤Ÿ�õñ&,‚ÞÆÛ>Ü Íá|(úü/>¿8[è^`ñÄþ©7*/ª-ó7lW\ I îÚñ¯âåÀãq+Ê·]U~þü»Ú\îC7lØñ)/òJB}íà[’Î7ıþf"ØTU7õï$¦>ó‹Ö?Ò‰O½h�£ý‘4i�!F3úþßïÛ êÇ›p¨êÍIÇ`ÊǹΡ´¢{8�Y[è^ÄÖ””ÑnT_3ÿî�WÅÕÌü�Û–ËU�z�|>Exõoh¸{Ç›æýæáÄ:s�wm¿£ðÚÉÍòÚ©êß6lØqi’¯]Ögsr~ÜE 6nÛ*y÷b`ÜEÇ£œ£!Gº� Êû¸û—IaÊ'Ð5%6}k��wÙ –|³ÊëQû‹…mÚݰaç»Ô¹sD©Ü/e—*Îf»Ÿ×p÷CßM:è{í¶¿SÅ�«ð«¤ã’²Kàs¹îç/¸{ç÷’§òÉÍéº)¯X8Ží‘Gcþ¦¨ª›2Ñ•j–UéZ�IÃ`õFJï3ÆŸõ_½b_8a•O\¿ÆŒGu\ß°´¢»³+ì½Âz#EKö´÷1B ²NÅÿË‚»v¬K:€ëü-°äžÅ'¿qDy50HõµrÓ­(×IWçWî{∪“²à®ïλ÷Ìg½T�_�ò¨€i«Âx½^º:¿:¿B_» Ò¡èUuMÔ‹ÚG¢wD惛2sV¥ªô½¢¼8®\Ç×nZÃ%-C&=zèñ&0ªwtüp—]à˜dÒ¸B­ È¡:°6R¦BQßÃl÷­ñ؃ðˆªÞ/ê6:Í}ÞÆ‡�®1”† ýøÅæ�zr*§—©ÈÅÀe @émq?$/·Íßôàe;ö8Í¿ûÁŸ?ßòÂSNw™ £¼ Œ!tm‚ü(´×®¬”nø½ªü^DÛfÔêON]·³+éÐz+Æ|ëܹWõL=ø:ç¸@•§Æ}ìøFCއ vßк:“ØÞ¼äß�½e“N:Ìkj[.ì_àòì¾5Ô<÷ >­!t þÝ+È€’TéGQçC==¥W’³¨Ï'Í"zè6ÁíóhÏЬ:·ß)]éÔ¼ß�ó=Ù|ö‰(ë”Kãow<\\µeñœÓA_á„óT98!‚Cä�‡(ì>ôgÙš÷lôùY÷–c¡œܵs'дô½vÀ�s€ã#8Dßk÷g`ª8ImìÉͼ¯‚^»ý@i,{(>z hŠ”~Ï‹~ûÿΪê~�B»"{ð¨ 9uÚÑûøNUé*ü]÷y•¼{Eüî¼sOÕTOy*îu"Q˜»uk°¶÷w->mÆôtU}6¢'©rÂ1À±i”™½�5LÓY ªÿ }½ é÷wE«@¦õ¾Y3µo>ïFE(~üà÷æñ¦âåU¸´óí'ˆ)¿taKàÀ~eEµ(—r6RÒ+¬7RT‹^’6,hذcæð÷2I[°aûŸ)t€¯ØÒpÊLªÜé’òswÊLUê}°°Gð�Šë@yÒÃã.ÕóX®ç¸'+¨›ÉôÚ5lØ1%é&ªnضhëý“ˆ«Ïž=]jêŽuÞ£ªÇˆKãUOWáù sAçÐo1â‘FC,1 ’ôöÿ`·Z�¤Ò¨Jp“Š´tÿþPv‹= ì¼Úb&S. îÙ¹ØØûgŒ‰*œ ØkgBõ‘;wwP™~p°Ÿ¿çeÇ'’z9È ¯êƺ—©8ÝŠ¾kÿm»¿žt &9½Ó·éÓâ|ÑéÅXk%��sK×…8•ÐcL9ü÷Ïž|øðµ·¾ú¨Y_{1^ߡ‹7 1d*—©Hz¯o:ø£Ý‰�Ò™Ê�F*`—Ñ*ݵ)˜‘’¸;Ý��< Ø}+˜z3ÆctËÿ>½¸ ¸é¥¯9ádÍñD_«p.�KH°Ñkö×Ô^ÃÚG:“Æ$/ .¼5%ª¾ht'œ5%1ÓÀÞȶa�1ƘQúù�{X ¬lxýÌ™tW/^¼Pa!Pgãð•C‘»¾\+þ«»°{ˆ�dÌdèî[¥Åƒy¶Ð½?/ZôV–g÷-cŒ1Ô=ßÛ³ø^ï2ª9þ¸ç¨Óçáõù ÏEø »Ô�Ô%n²ÀþÞéÐ{zoë!§J'ª]"’ÓC»HJß”ª‡ï?,�) 5½=¨Ð=øý¤GÐC»ÛyÑœƒ¯t8\§ˆ>°ïOn£´1E »o6ëÁ—¬j e¤Ä:ÝýIÉÈŽÕ)1Æ¡¶6²ðÄÀ 5uf-�UŸ=èNDä8Ç ÌP¤Z”©*R§ªµýï/Â4Tmy¬h•À´ÁÚV¥CDrýnèT9¼]¼YÑÃ}sE÷ˆ Š´ãQM¹=âsÞ{Ù+*ù´¸}=®'Wí}Gõ”ê쮵»Êׯ_Jj³¦µ¯mß;ü��›4â]p}åÒ+ì.˜‘’¸–]¯1Ƙ ¤·“½ø}Ò±ŒTßâ‹©¯;î ñþMÚ»†F”ZDzG~ÔÁá­À¦«jºð©)Œiá¢nÿ-Ãg©Þ¿×Ò7’ÔYȲ”i¬´¦—1ã–g‘x?R²%°�”ô ¬“_Z§Ä¦ocŒ ×Ô g_-âúÕÇÒ™ô~Ñ•$SE¨(ŒÆô&B5ÊÔÞ§�é‚~­ã¶Ýo/=–óþµ ?ôÅ)0T7 °Ùá¯Ø1®@­šzà˜£°ëñ1=Ú˜aº%°�½è^�$°¤DÄvß2Æ3aˆÈ‡GŸHÉ�Šyä^}Z ÿ»íð:Ž¥Tk•¸¥ÓKJL,\�ÁÒâ‰ÎFJJVÚ„ÇFJŒ1Ƙ"§LOïùÂì‹fO˜}ÑìéÓ»fxn¹cñ©|Õð÷2fl¬S2`e~0kJâ�³táx¥óªE³Ó¶ÐÝcŒ)¥ÈÛ;•7L{í±u©> ¨I&ŽªÔð÷2flÂÜØ•Œ ø«8¶�š”níl#%ÆcÌ`¦‚>?Ñ ²Þ’dRâµxúV8Ï!”8Ë$°$ÊcŒ™ÄTÔ’‡¸û®¸3ä´XÖÉ/­Sb#%ÆcL¥J[Rbbä‚ìлґ’`ëǼ¦$´¤¤Dˆ›.cŒ1“„’²¤ÄÄÆ䔢Ò+ìÁ$V¶ûV?¾tMI8ÓðŒ1ƘÉ'bŸÑ„"�tcâ�/ZŽ ~üIÉ‚eÏç¾úˆŠÜ�¸Ÿ²qåöq·[,Þ¤!´‘ï‹wßr1Œ”44ŸB•D—´n¬ß -~Øû™äØ{nŒ1Æ'�â‚Ë{l};Îu1�ÍoÑÏÓ@åÔC¦i»ˆÜáUï ÚýŒõ+Ÿ×qˆ}D'¬¤dÀHI ‡Hé=x­�ª=ÍtΤ�½Qµg¢')½¯Ó¢jOç>9�­ì�ª=cŒ1Æ ”Ɖ”Άªx¢¾x¦Ï§ý,¹¬ÖuÔ_­èò!î1GU/¸œŸ'Ót�ÀÞ¹;hÏýšmkº‡x\BB{#U‹g´ÙBwcŒ1ƘÉ(�zܲ/Z²ŒaªÆ‚œ,©o+œ9ÂG¤€ŒBF¼ÿõî �Ë«"w z›Wofø‘ŠÀ^è2³…îÆc¦°CàÔ¤ã0&DéqO}J‚P<¿{´Û/\þ:Q¹˜YÔ¬r�GoFä|Q=‘ó€ª!Z™‚Èùç#™¦'~åUï Ÿú÷®üë ‘Ç© Ó¡¢UZ<ѺcŒ —×¢¬I:ŽØä›&n‚’FU‚ËKD´x¦Òº/iI»ŽöO«òaK96­nÚ®fîû§1¥æ…ÎûóÎ2Ghý8…¥"²”tÉz”ÚšÛ¹ó3£x†cãKJ¼�6RbŒ1&\û�ßõùi�ó^àIǃû:N|rSÒA˜‰+�sá­)ñ>_”H�¤ÖÊ‚œ,íßRX<âãl½a?p‡‡;X´bê_.ªçƒ¼8ê�>¼¥«»“LÓo€©#>öX Ø Ây-ž†çlM‰1Ƙ€ÝH–צ.…üz`zÒáD¨Ë —q#Ù¤1WÚ©vy§EË7†›¾•i¾@Яpä$bx…-‚?¯ðyhq4>½ ßT¯s�š!Y¼|\Ç™°ÞJ -dcŒ1æHößöøÖis!Ê€ÈvLPåíoÛµ%é@ÌÄ–&þmj£'^K6Ü<)YÒ’fÿžOˆêÿ#òçÙâÙÌá©^™Ë§à¦¾¨ßT¯Æ!ãŠOà=|[SbŒ1&|û°ëWõ×è�ÿ¦Â¢¤ã‡]мíÀŸüIÒ�˜‰/Ì5%>å‘~kÝ«è>ïÊceû×Q^V–˜Ún"Þß &$)[ÖÜ 1/ó�-tÀFJŒ1ÆL<{´·}ÿmOþóþŽ©'€\‚ð] R 0çP~ |ÂWùS:nÛýKHLÒ0†Âƒ‰+Y莙Ír_^™TT#`uJú+-‚iŒ1ÆLdëvví‡ïßaé3ê¦ì^HJÎRõ/y.p2Ps�£ú" ò몚ìÏÚ×¶ï�ù˜Æ +�tcâÕS´#0‹!÷^àÄÄbª�%%¢Z”§¹7]0ÆcÆbí#�ðk úÈ”‹fŸ�wŠ÷þdAŽ™ ~6ÈqÀ쿵˜F¡ÀsØìö+t8´]‘Ý(O‰Ó'ñn¢»¤§ûwûþoßÓe®ÆŒ@Á…·¦ÄkÉfZ¯I*”QŠy\ ôé[Æc̤¦°ûQàQà·IcL9¹ak|T"—öÃß©"Å<}+´ô²dd'ÄõMÆcŒ1fÜÒAÖ†Èå5Ð5ÑñíCLJúÁŒã³(;A£«ª[«¡&ÄÆcŒ1+�â‚[lì°ŽáD ªÅiTô£vÚ¶ª!ê6�1ÆcL´\€) ¸`¯VÛô­þ|` ó�1ÆcL,Â\S’sÖ™\د‹ p*¡1ÆcŒ7äš’p§oÅýZ‡•” (‚æB!cŒ1Æ3> °6DhE‹yúVh[Ûî[ÆcŒ1\�SfòÁŽ”Ä+¸5%ITˆS �1Æc̸…¹¦Dò�u¾‰y¤$°¤d@¼&ÈÆcŒ1fÜÒÄ¿Î!z.í v®8…6}« šO¡J¢›¢¸±~'´„õá[¼ls©A6¥º練9¢‘¿Žcyÿ¼�vJj]ú­8Y{Wî ´õ_&K.«å©ºº·W×ÔâdàíqjÏý•mkºÇôØ3¯8­Š®2–ß»Ló Ï<Èl”©ˆÔ9ÕZ ‘Zïýíl^ýÝQdz°éb‡j©TîÐíyõà÷ÝWµ“tº«è6éÙK§?|õ’eë ûGÇDµhÅœ¤Cˆœó8ØÓ3¢ûúy³N‰x ô+=´W:^^´hYI S %¥÷àµ>ªö4Ó9“6öÏÌm©fÊÞÓQÿ\¼ü�ÃÏFäxUŽf#Ôõ U 3€Âe©P]=ø1r@³!õìƒ3¸“Žáî')½¯ÓÊÓ�1¨ÜåE�wå4î»æ@d Æ%ó¡ÙàOÀç�E\=øz„ µˆ¤�,Jêá Ð�в؇ÈÐÝt§å¾kž,ÿ @¡ó;ôxTOt"Ç£r’¢õ¨N©G˜LúÎgu~ç‹uƒþº+”î-3�ž^lËc%Ÿúø!N\cˆå´eµÃ&H‹›Ž#ËEâxÊ9 Çúý¾šú—×RÅÁ.£NJDùP…ôË- ‡*ù*,½é�ïs¦©ô0€þ�ØzÏú½Úûý=‡êás²H—@gá69ˆpÀÃà ÈAнˆìÿRú0Öì;ò3./ñþOÀàãBåú»|0?¥ï®÷¶>ÝÎá]¨vâè•®Þ÷¿õûA²^ôi�ÇQÿ8¤#íŸdCëczN%Ò8‘à.°çÕÇPg¯l÷­þ,ÌóM�Ì’Ëjé˜ù2¯Tô|h?OQ´ïã3àU ëm7fÞ•Ç’î9Û‰[ âPy.ð È\'>cCtÌú9t=ôŸÃ¸UYÈ4uEù£÷zåR©ß²ñÚ¿DÿÄ*Tæ#õÐõ ç(¼˜zôá´ï+¹÷†�O‹•TSì¶!~Ö¸ì…NR+4§‹P5�N©S{ÿô™uÄ{—|Ö´ä6)þIïï¶@N Óô°Q•_!þVÚÖ<8žÀM,\HŽôž" }ïtß_ Ÿ9týؾ�Údšþ*¢ßó¸ÿaÓªuc 0äŽGâÔ:bƒ­x"Rü>†øYŒÂ¼+�uU¹+´Cÿô˜ÀÞD¢Æ¦yNäU}’ÍNªZŽe]5À„9 ¯|2M¿åvïø_N}ôç¬]›�;�²:ûÃÓéìy‡ˆ^]çéIñ{îó•ó>vTœÖÙÐ|Š8½á jkÇë8àB.÷2M¿Qå:žõèÿL¸ßg3˜“Tå‚~€LÓ¹ŠM«þw´�¤ ßB�ý.:| ûoÅûBûÐ’¯ÅW_']�¡±é="Ù«u¸+Xƌע÷�¯ú;�·/P´’NýÏSáy¢¬`û‰OH¦ùÛ½…¶Ö I6.‹—=Ãå݇´«û�‘M# FJ*§3š�Qüio\þF½&áûRçˆpÛOü£.l¾ŠM3¿ÜzL3V‹EõÇdš~®©ü{¸ûº#}`˜kJòꃋ¹B«S"êÚ ñ;ûÃÓ¥»ë”×&Š™à4Ÿåœ^¡žKbµA…9NÑeËXØ´I=­tÏúªj– IDAT6[[F¶À³œµ¢Žÿ�’ã# S’'1ù š¾5Ý)ilnÑk©¤´|âzލ~�…íËT¯xm×ý!é€LÙ¼Tò©û´±ù£l^u#˜âäpNXjW6Ö×ZJ‹LJ÷Y‹ÌGꥫû—¨XBbâ³`ùˤ±y�8ý­Â[#!)¦,á«RÛ¾�LóûÈ\^•tHÃZ´â|éÑß \ÅdNHR®rFJr�…ï—ÆåïÑ•XBR^ÊYBj �ÍÍI‡bÊjšˆ^ç2M·pÖŠawÿs‡WÏÄ:y+ö“``I‰/Ùq“`úVæò*‘®ï ’ÅLP�Ë^(™¦ßˆ“;=/ép"r’ 7u¿#Ó´4é`µä²Z—i^%Þÿôä¤Ã©¹ )éN9›Ÿ'"×%Ê$V+¢+]¦éË,¹làÎqfÂRx³ôäבi>áH÷sAní‘Ë…Õù6C˜ŒÅë>†ò²¤£0Ђœì6}SÄý8;épbršÀ·%Ót‹>ø7IsÈâ¦ã¤£þŠ6aWà«JUÎHIu6Õ»†dr�^U…wȾ™·3ïÊò×È2 ’3ý%óWœ4Ô=\�ÁJÚfptb®èàš’þ4Ày4.�À'’ÃL4-ŽÌò&qéTy“£S|¡øü46½-é@hl~žäX¼0éP*N®§r’Ÿz7pNÒa˜^¢çIUö‡d.·$qr9]ÒþgC�˜8„h«—ƒ¤Âê|—Kh»o•&Q|K`QùPùsâM8šO‘Æö_ ² H´ÈdfˆðU—iº�Ó–Õ$AæŠçŠè/€S9~¥K×TÌDQùç¤c0,j¿Ìä¸�b{Ž ƒŽ”¹ ;‚•4Outb) ,)™L,›¼.é0Ì’i~½¤t3‹’%I ï“zùi¡(a5,?]HýŒB}3˜J)±i[J.¡±écIGaÊnž«Î~‘’~q˜kJ\°#%VѽÈäYSâœ{/v5ÈD%Óü1AoÅêÛô’s…®uÌ»òز®¡y¦¤ä6àIJ/TiêDSF"\EãòLÒq˜òRåMd–/ï[š;J©`‹'Æ,°5%ÞkQNâöÔ#±ä²ZíàÍ ý ÐM!aÝ“P uXgeŒÄešV)º|ø»N: R•ý¹žñ¾s¹ÿ³í±eéÒ”l×[€çÄvŒ‰"WA ÝM%K‹ÈtIË™¬kÉ%Œ)A>­‹>x+¯ý @Á…v}É*¤’Ž¢ò”.¯xN‹wBœJ8ûëÏfÆÐòŸù¾ßî/ˆ<†Ëõp0w€iµ]¬_ÙÃ1Ëm'£Ÿvq DºVn'Dxdj~Ìm¹LÓ*TBBÒC!ÉÝßûï* Ó˜’®…2Wªj¾«§-{%ÛÖtÇr„í'þ#ðšXÚžhjÓ–”˜‘ZÀþ=ï¾”t ¦¬¦I>¿Rá€taÇ£Àú²Yõ!.ÏO:€Š#^‹_–‰Y§Ä©¼"âÒõªÊ'xÖ£WëÚµúK_ÛZÏíc$ÓÔA„‹¾µ+{[oØ?ü=c–iú„‡8r•ߨc^×£¹?°å˜¿@Kir%dš�Gô¨,ô|à\ʽ¹ƒèy®Þ}ÎÃ;#o»±iž@KäíÆ£o”´»ðwÍ�t ZIr²O”¼ÂÅD›ÈtVÔúϧ9 tEØöPê�æÿ3’–澚¨~:’¶F¦ME^¢m«ŸãÛV}’-+7 w•¿"µ»bFJLD¶¬þ™¦ýb`GÍ+jëµ*M[ëoü¦ÖKUÝy–8œÀÜ÷OshhÃ$€KÖù.—ÀFJJ§›¹ 8}Ë×i{cž¿l‚”Ùó^”³Êp¤í*ºˆÍ­ÿkÒ»®%G[ëÕêݹÀñ§—*o¤qŹãn¨®êï�A+G,¯h³¶Í:“M«Ö•áxñꨜé[&BÖ<¢ä_Ãáu9Q:†ÅËÊqÆŒÖæ•¿V—= ¸/–ökkNu7w H8R²knü¯spÅK’’‰¸û–èÑ‘¶—Ï%¿àºòö{0„ÌåS½ª GºKI/fÓêÊp¬‚-+7©Ë.&®/·ÃDÐO�«…ÌåU¢4GÏ‘t«ð&ÚV·Æ9w½¬æ´OŒçaj»îŠþS,m÷pj,íšñÛxÃãêRODÞ¶øSÃ,žØY51:Qó��” Xàg±ÜDž™t¦L¤î]@´#mÝ£yy5mÿµ;æã ´ñ†Çµ§g èݱGô<5-ûã§\Äþ{§È 6µ~'îã”Ueî¢d¢ÒvÔ„_EÞnJN‰¼M��×þEaYäíz=Ú!Á­‡ªGJ:µ÷däó‘Î�wN^e{¦r‰rĖئ٪Wö.FOÆýŸm×Tþ5Àâ<Œó¬ëcEõeÐcÀZÚV}6îã”YxßÓf”Z¼æcØP]ÜcÌxMŸõ] ÛlGÈÕ» §ºÂè:± Ó·[è> Þ 8}‹ÔÞ([Så�44ÇQˆÑT’ËæoÅð¬:Þ2­~ãu÷õO©sÀ¡‘Sxsß?úÚ5™æ€ó£�¨È~ŸŽáªcòl”d2زúg ÷Fܪ­)©t…µ‡¿�´MÑa®)é´mØô-|Éš’ X<±{æƒê D¥Þ¥te„í™ ä\êâ8ÛWäŸÙغ1ÎcŒÊÆ•ÛU¥)Æ#L¡¶ê¢Q?Jôb }8‡©ê5lh�~~vò,)™$>iƒbII‰t„ÛA IIª&°Î7Ðý”m \j2ì¾UØÆóÏQ6©p™åñʪé¥Ê‹clþAºfþWŒí�ÍæU_ÚâjÞÁëGûQâž.ÙNw®òÞ‹hØÅÃÉBôçQ6ç¼ZR�$Úµ>Ì-�kòv²LhÓ·&C�@�ŸÆÐæjרô -;�³VÔEݾIÐÒ¥)D3q5¯¢Ÿ¨ÐšªHlµ@F·5ðÜ–j`I,Áôå¶Þ0QwÔ³‘’É¢sÖ"ÝõPë£kËÄF\ÄÓÝŒ4Bx Ý÷ç=µ�…ýŒ¡#æß³àvßš kJÀ{½U‘OMQáRñîRz|ŽLÓCÀn`· »Á?åq»íÀkÎu šCØC^=ÂTs¤Ó¸\s8º³“u7—£¨�9’mÇ?Çè×?ŒÌN6õí˜Ú¿®™?¦¶}íçx–ŸÎ=«G6rYÛ¾b{ðð…8ÛO˜]<œ,¦î>‰|*²ïoµé[að¾=ÊMS�’.lÃV_–Ô�vš~tÏ’ØÏÑÁÕ)™$¶Ìú ™ö?Ï�éiàY½P�ÂÇAéýïýh(зtG¼ï º:ª!Ó°ŸÂ/Ø W;÷öþ{¯ÀSÀnßû”§pòùüŸØ²fWLÏor7'®¦Uõ抮�±µ¥G›nWáÒXÚO“a¤Ó)…†˜Ï¨mln�»NK’l¤$I Í3Éwî1¦Su¤«j�øŸT†¸  U¤ÒÓð: ÕÐzD¦;a®æy]”¡£25ÒöL<œëŠx)sM:È«ÓSóžJœ|�¼°’-þ@øY‘¯4}Jà–¤#¡¾«Ã³JÐ÷nz£„BÂãdše�wú}ªRßgýÊÎ2Ä:˜°~J‰žßR¿TްIˆ))QNé]�gAœg$~öuX–”Œ×¼+§’ê>�”; å§r,Ž™¨ÎRÕY8™‰ROa3†zÀþ¿Bªº¸½a÷rG8õÈáÇË¡ÿij½ŽPC«&j>ß…D8kI¥:�ªW³®¦]é lÊaÏ›ú_lr¬)`ΣßâÁ›Æ^Ä­ò¯Â¥¢r)=~—fš®eúÞU6%ltî™O^õ0›Wn‹£áH ÷Å•V:u§�t˜H§Å™ÞzôÎøZ¯•;"WÉ–\VËþ™Šê[!ûjp5}ŸCíwe(¼I.£`IIR©®ÒMTÇE´Æ!Q¦9e²ë;Ù .¬SÔdØ}«ÏÚµyu¼8�t(erŒÀ¿KGý4.UÒÁEýQq4+*¿Œ£Ýȉ{4®¦Uô¤‘ß™‘ßw ¡�×h÷ø¯<6R2K.«%³üJé¨ÿ«¨®¥°[ÜäìœÛHI|.ê Ž5an \ûhX�o€ìŒ2O l¡û€:%x¤`SëU彄–<ŽÏ³Dä]fù8mY˜_43§•õs©n¨9ÝããEG»‘ó.¾š£[@ûŒØâ€½“` –]<©…MKÇÌ?òŸ@,%‚¢–”!Ÿ¶¤€9íá�ìòã�KGÂÞgq´6¯ºEÑ8‹ÄU$EÞ#3Ý/z+d‡¥«Ì£ÉËÎS òP,íFmúSñm‘[˜?B¬;oiÄ[iV"±‘’ád.¯r™æÕ¢üèÉI‡S9Ô’’TõÄ�”„xuzíÜÐ;ßñ­N‰¸°â�JÛê5ªr�K:”²RÎü:7ó‘¢½hѳ¯ÜIÉ‘wÈs»þñXÚ�ÚºSâÜÆdd��Ó–U§ñ�‰>JhxËéŒ÷Íê~¡¨ÂÀYR‚.편ŇøðÖ”Tò––C)Ë�À¦oIé ©ä±Ú¼êzõú*àé¤C)/y¶ääÿ8ã}vöŠP´Whë\yÏ‘qÕŽr¡t[<ñMýYÂs\]ÜIÉdX[f#%C9kE�TWßœ�t(•ÉFJ‚P=#êót• ¸#V¼,))�W'ðB÷ÁlYý3õù3½-éPÊKçKuõ—b<@´'Ê|m™?—à…¢-¹¬b+ê;²¤¤jJÌÛYëd(gIÉ`–.MI·ÿ&–�IÌL$Ò£îsº0×”rÕ¯W®3Ô×9>¾dú– 6A»-×=ª›V_¤ðV ¶]‡*ÐëYØôÞ˜ÚŽ¶3”-óš’¸vDÌ§ŽŽ¥Ý¨ugõ‘%ëZrÀÁãˆgÝPe ë;º\¶Ÿ°ᢤèp“ûÂL(öç£þw 6) kT 7Åvß t÷­I6RÒ_[ë×5[õlU>L‚ùæ ʲ`Ù114퉲æ`¹¿ ÷ÆÒªø¿‰¥Ý¨åóq&%��â¾ñ¼a$ˆãc#%¥­˜#ÈUI‡KJB�šuŸ3åpÁ&%v¦”,Q3Åî»æ›[ÿM«Ýɪr±¨~ ¸Ÿ‰» ~º÷±Ú�¶3”+ïšQy*Žv�Êiq´9çcë°‹ÈÈG#…�qÅÃüqÖA©õ¼5fâý`JÒq •tf¦F?R’FƒÍHÃê€ç»%ö_³ÐFJ¤yZ€I;î㔕³¤¤Ÿè ‚ªŒx{kcBaÓ·& mMI ì¨�/!ºÚž=Ãßi4d –E½ãذùRœí ÒBãò¿�ó#åj«®æÄÔüãìÓ¯�ùÑÒù-â/rX+ª?žH‰‰Øî[ýEÕE8åÕ˜ rRÖHI¾¦ Ý)ÉåÊ×<ê™Q7é‘Ýѵ¥{£j«W•÷±ˆÛžÏ•ˆ§¢•p"òU4ŵ3ÐÈ46¿Uáïãj^áz¶­ésm7fýt„! e†¨ÞÁÂå1׳(Û}«Ÿ§‡¿Ëè8ïßu›Æ$-ä¤Ä:´¡³é[aòùêHèÈ‹Ú G¢)šY¸üÒ¨Û=¢-kv ÜóQ¦Šãv›.Œù8ƒ[ØüfýrŒGx’´_=îVæ<öàžñ‡3¬ZQùŠd–¯eÁN.Ãñâ£b Ý‹´˜€Â¥e?'3G¸ûA‡5RR—�¿ÃÚô-Û}+D"ý¢Ü|îÞÈÚRõH €ˆÊ×È,o¢Œr¼÷WC„SÛW'ÂwÉ4}´ŒÛp ™æŠêW‰±Pš¢ÿIQεkóªn9e»&—ˆKos�Mß`aó%d>R_žãFÈê”ô÷p mЍ|…ÆæËbhÛ˜D¤±‘’òðY�ª˜ZRâ´dm~¨ŸÅÉ#Ó¼Õ…·úG¶\áây},º¶Š¤YE¦é"Å5Ó¶2ºÑ�¡lY³K›þC„�ùHi�'Óþ u+ÞÃÆ•Ûc;ÒâeÏ�¼»Õ—Åv e#3ŽúïÈÚÛ¼ò×ÒØt½ WDÖæ‘¥U¸TT/….%Ó´ø�À_¼È¨>‰jä B7*yRRHÀ¼ï(ülž}T¥òxý¹×vß:DE׋ÊëchºJDo’LÓ+¼ºO²yå¶ŽaLÙ¤ Û°Ù k¤¤,[S"y-ÚüMm¤¤bÍ»rª«Ê^¥èЍ›ôÖH?¸^·“Šõ£ôRÁßK¦éÇ*ÜD•û1ëWÆ·öcƬÏÐÑþ Û1{‰xÿ{ilºÑçª>Å}×<YË‹›Žs9þQs¼˜Y»ƒëTÉ¿ƒu-‘vŒ}�û°ôø%@¹ë¼p*pªÒ¯$UÿS¦(ô|Éé4øØ¾JmúVŸZ�Ä×eQx³ˆ3™¦{ÝäUv ìAÙ‡cÈ>¼|Ä5åò ãMŒ#©5“R:à)3auÀ}µÄ²mú–©`-Ž3÷�F.¿À ¯R²+ÄQÙ\½KÝi‹Ù£"Õž£0.åzüA2Mw ºÞ+&•ú ù|;Nö#.[ô¨�+w0ÚÁº–œ.\~™¨lj"{C«Vá ©Ê¾‡ÌòªºoP#ëX¿rô‹u3—WAÍK²Ts¼EãOFPôƒ½5F¢µ~e§fV¼EðwQ¦ç,)9lFûF:꟎ŠùH Š4êÕ ½gº«ç•кSfâ yú–�””ò¥k4*\ÎiÑŠ&7�º74ŸB•D?_?›Oá˜1àvçj©p»×i Uˆ¤ñ~:H=è'Ì@äXUNŽƒö“É3•¸Siå66®Œ¶ÇÖ–2Í?}E¤ím °D‘%"®ç9•œ¬°˜Ô•óÄ$ð9m[ý¹Øжò~ml~»ˆ®%Ü5™ñ³:%‡­»¹K2ËoVäƒI‡bL%KîI5¬¸Ï ®² ('Nœå–xú–¤ô^¼LÆk¨iJªGžÍ§ý¯œÉ¡‹ie¦*ü[, _¥\IIylZõ%É4=_áC ]€¹*Ì)|b”’Ϙ;ôJìä,ÜîµsyìÇÙ¼êVÍ4ý“ÀÄ~¬PY�’">•þ¼äó+÷B°1q“�·k¤ÄWÙî[¥ì¾5�GJÌŸ§­uC,�oZõ ž¶äç<úA¾�t•I~¢Óöþ-m7f‡¿oÚZ¯ÿvÖښ’þî¾öOqnmLð,)™PB[è^šDYR2‰<ìÓþ#1¶¯ªú 'Æc”ßÚµy?ç¯oøJÒ¡TáV­–×³îæ®rÖ·µ6[b2„‘ìþ5Éøj÷! ºÍ#Œ™`\¸ Ýë€×æÊð:6RRjOß2E¨ê"©q$›W·©ðÑX�‘„µkó¾­õ2E?‰íp„¨|F7ÍZëhCÓBb¢×&pìŠæ�³¤¤Ôú•O«ê?`U�”£hOÖ�ĸ¿^¨‚Ÿ¾e&�¼ oaóê¶²mSë*A£«UQ9”¶ÕŸRük€'’&!O«p‰ß¼ê#Ð’ä÷�ú¶ÕR•+Àwb#%ƒÛ¼ú»*4%†1•(Î-3ãù—�*¸…Í…m½ö ð"…BTŠ-T‹vt¢Ò;UÀïÅ¥=ä²äe?âàªz�¬Ò™ßC^fFoðrNqEûêš ›êR‘·³iÕÊxLõsû·ý¤о±ŒÇ-�¶5?ѳV<ßuë*}{Òá”�p»fÝ{¸wå_“å�Í«®×Ʀm"|˜•t8‰Sº“¡bmj½N6׋ê§w ½1‘ yKàþWÙ÷ªø—€¼Ày÷}È\ГGÙæéªz:Pôª.TÕïE×ø]o*×ïq©ÞÛRP�*Ïv4>°)m’×âÍßlMÉö´ª{=m+]ö#¯]›÷K—¾Åm?q·ÂûË~ü¸­_ù´‡w°°é«¢\Kù û•Ó겂¶U·&È 6·þŸ.^6OrîKÀË“'Q½õÌ6­úWm\þ;¹ ¨O:c*ÁÄÙxÓš-À–¢á“ÅËf�•Óq2×)ÏW˜K¡*ò å ³Œ$°yªâÂJ¢ÌXýŸ¦ý{ØÐúHb¬]›÷ð2ÍôÎÿ¯M,–¸ljý©.]ÚÀƒ'¼[D> œ”tHz\U®¦F>ŸÐÚ‘‘Û°æ…W’izŸÀ¿G'R2Ô’’ál^ý]mXþ€¤äKÀ9I‡cLÒBNJúwÀ¿Â^XHÛ´õÖÏxß,ª«ç¢ú|'2WUŸ�H0;¶hÍ@â´¸¸œ-tŸ`ÚUø›Zo¤Rê µ­ú¬66Ý)¢_93ép"·vm¸QO[öefÈ߉ȇ�g%Ö8üNázªÝMŸŒSÚZoÐÌGnqt]©Ð LK:¨ò’­l†IDATƒIG„{VÿYá\2ͯô?€ç$’1I™(kJF—XÝÿÙvà7ÀoJFVžA6] ùÂPjÿªØêëQÜáJØ€“©(Õ½æ 5 …u)ªÓ�´ )”〳GùG«2:~#%^‹"V›¾5Ax�/zï?Ζ5»’f€Í­÷)-gѸç"þc ÏN:¤Èm[Ó Ü¨K—~‘Ÿñÿ^àÕ„qj¯À÷½ÈMlZµ.é`Æ¥íê½þgø3t÷,ÕwRø˜øç:±‘’Qi[õ=]ÒòCöí¹HDß ¼Š°ûhÆŒZÈ#%ý»³Ñœà7¬óô’açM�yÅ©’Omkû#"�­)1�°^ón¹nY¹)éPެų™›•–¯Ðøô+œ¸W)úbà &RG 0rr›Âmdšž r‰¨^ŒðB*ëÜ¿SÐ;¼¦~À¾ÜO´�TMw~¦ø’—X´b^_êÔŸ¥"s)ŒdM¼QzKJFo]K¸UáV2Í' z±S^¤ð< Ÿ“é GhL¬&ÊB÷JúrMŽlKàœS\¿tÎÅ1R";A£;‘×êX×íìa`M‰öÁï*0ØvšzpÐmDz„A• dï ‡Pö VqÙ«v"2H:É¢~ÿàáJû¡6ÛZBP#v-žÍÜîáv–´¤Ù³ç¤ü©¨;Ñ£�ê,DfQ M«jïÎJ® ÑÑLÉ™ÊÔ|rë¾ÚZ®U¸–Eï?M/q*ç*œœFy:•ÿ´Jb¯—ÚH‰äµèw0†â‰Ú¶ª!ê6ÇBÛZG»ÜèÚ�³ñɤp¥rgï`‚V9ÛxÃãÀ7=|óÐm‹›ŽÃë³È§Ž†ül„£r4P…Èt¼O#2TâRHf{·R÷°až=Àð»É§ÿ³y¼wôÆ>³ý^“'¦Ú÷¸?‹}�Ú4GA~6Èïæ�·éú¶Ç#mk}nT¡L…uUíý3¨ s¾g%+mk jZ®¶µž–t c¡m­‘®Y w¤DÕ÷ =Ìç¹ÀFJÄiI×ÄÞGc’²¡õ J 1FÞÁ¹7êͨ ìÔþB3úº�‹~íW�LÖ¢žÆ˜1rÛ”°¦où\^çÀ’’�ý,cL ²ÒIôùg7ëW1=ÕcnR"#ظ’hºòc,7ɇžDcLتr/'ò {ò86CÏ3Já&%޺ܚ’Tq¼ègÑc‚ÔâDõŸ¢oW�¾McÌDçµCÚH‰ÏÇcp»o•Œ”8+žhŒ1e“i_�°(†–‹¡McÌîH F}VR"®d¤ÄŠ'cLYd–¿Dà_ãhZ„ßÅÑ®1fb 7)QW¼0oéÒÊñ©.Çš’ÀFJJ“,)1ƘØ-\q¦ ß…xêOxØG»Æ˜‰-ܤ¤týÄ®¹•ý<Ê1}kàèQ`lú–1ÆÄ*Ótލÿ)Ѩ\Ê’cÌè…»¦¤t Ã]Á>�èHh#%¶û–1Ɣ͢ç ܽ•áã±£·æ�1ÆŒJ¸Å-Ú«û©Ê~šH%Ee‘”¢ýrK[SbŒ ECóLòÝ…sV]j&ZU|þÊçgý;%\ñèDÞ§� BªÓŠorUHémZí�©Å7Q�p[ èEï/j‡rc'ðm»ÚdŒ‹p“-)9&íØ–P,•"¸Ý·rŠë7Àe»oc!)}”TuyP°ôtæ»ÏP�—þ`°S» ¼U8Â,Þ²œ^³Þ¹Ëq cÌÄîš’Ò³{Ï�Ê~å(žhuJŒ1Æ$D�ظr{ÒqcÂîš’Ò‹D]éÊ~ZŽ…î��” `#%ƨû}µ|,é Œ1á w¤¤túÖÌia>�H…V§$g[cLøžÖTêÖ¯ìL:cL¸ÂMJJ'ÙvIe�”¤Ë1}+°‘’Òé[ÆcB³O¯âîkÿ”t Ƙ°…›”ˆ�”ôí‚2™ùÀÖ”¸Ò5%6}Ëcr@á6¶nL:cLøœÂ�ö¤µÒ�¦ê*|¤¤,kJÓS2}ËÙô-cŒ D»zy9m­¿I:cÌÄàhk]«>·ëÄ"%kJò=•ݡՔ-t/5`¤Ä’cŒ À“êä¥lYµ>é@Œ1GataËõéôY/Q‘«èÝq=ÅÚ¬«ì‘’rmMÉ6}Ëc*\›:wWÝ“t Ƙ‰åpG~]KŽM«ZTý9@åï3^ºûVu…'%ê­NI©»ocŒ©P*Êuº×ŸmµHŒ1qؑ߼æ.¥¶Qàë Ä3r¥pŸµ«ìÁMßJ—Ækï¡1ÆTž_¨—³ýæÖel[Ó�t0Ƙ‰)=è­mWïõðV6ýŸ(×ÓÊÖðçµÿ ®\#%q¯Å÷¡Õ)É*¤ÿÛ*ºcL%ÈUõv�ï²¹õ¾¤2ÆL|ƒ'%}6µ~Eϼâ×âS· œU¦˜FFÕua}UewhÓ)‰¿¶¡ +)éI+ý׺;KJŒ1¦LòvÞÜìäÏ^t^·Q“º›õ+ŸN8>cÌ$sä¤àîëvè’–³Ï'Dõÿ1Ø”¯$HÉB÷ê ߸$°‘’RV§ÄŽ®Þ?¥öBÉšGè rßTsE·ˆäö ¸§HñÖýªÀ¡ÇzÕNDº@óhïãû£Ú�’%¥O“Kµ“ªi§íê½EÍ ö �1¦Œ†OJ °Z´±ùW"úà¤X£õEK|…×ѼTJ>W1$«ÅAÛØmk=*¶¶ãjØc*ØèzÉ›Wý\I7 ü ¦xF®tûÛ¼�”·%ðÀ…îÆcŒ1fÙHImÿµ[áõdšÞ'p P7Æc g¼o&SÒSÉ»©x�†2IMÁå§â™�0dŠS™Šc&ªÓ¦ :M•†¢Öª*<)Ñ”Øõ¯.¥øÃeqTm(ÉcŒ1f2}RR ´µÞ ‹VüR¼ÿpFÿ:ô£,lÚ�2M‘zЩÀT`0½÷ïS€Ã3bEzgcùÂ,ž~yT´¸??ØÒŸ³©?V§Äeoyˆ1ÆcÌd7Ö¤¤`ãÊ­zÚ²E®Þ]¥ð�ôNSXz8‰(S?YÓ•�”¨—Ø –{Wº¸2,¶û–1Æc̤4þËÔÛÖtû¶Ö�*rðäøC•vàà�hO¶ÌÇ6ãÕ£ÅI”Õ)1ÆcŒ™”Æ7RÒ_Ûªé¼+Ï�ªì—€ †¸W…D¢èìû·@'*]ˆ¶{‘vT;A»iÇ»vRtâ}ε“˶“ËwòÜÝûX»6?Äq*�¦‰y à ؆24–”cŒ1ÆLBÑ%%÷]ó¤ÂkY¸¬ŸêBô==Ã%cšàuÿØÃLDÚKð)CÔ\z‹…Œ1ÆcÌDmRR lZ³¥è–Ј8¨“�õ´¢XñD×£øª¤£0ÆcŒ1 ³­�&Z�’ªâxÕŠ'cŒ1ÆLF–””‹ú2t¸Û¸”³¤ÄcŒ1f2²¤d" ­¢{géH‰­)1ÆcŒ™Œ,))—rŒ”„–”¤ºÃŠ×cŒ1ÆÄÂ’’rÑ”Mß*åªK㵑cŒ1ƘIÈ’“œT—%%ÆcŒ1Æ’’²I—aúVh»o•²ŠîÆcŒ1“’%%J`uJR%Ó·œ%%ÆcŒ1“‘%%墮 Ý]XIÉþ’é[6RbŒ1Æ3)YR2‘Hh#%5aÅkŒ1ÆcbaII¹”¥Zy`kJ$%V§ÄcŒ1f2²¤¤\Ê1}Ë6}+}°$^«ènŒ1Æ3YRb*GYF“Œ1ÆcL¥±¤¤\ÊQÑ=´Ý·Òu%»oÙô-cŒ1ƘÉÈ’’‰D[S²g¿í¾eŒ1Æc,))›rO n÷­)aÅkŒ1ÆcbaII¹hª £�-t¯ÚW¯�”cŒ1ÆLB–”L$>°‘’�,)1ÆcŒ™„ÒI0i¨ës#,XöËáž÷<ÃÌææÜ+"²©”¬+-vнkäcŠõý@jP;ðwô� [‹ˆˆˆlD*%+%Øs"u ¸l{¬öA÷dpzƒÑëX õ/€BÓëõœ‘ I¥d¥T/Uýààî0Êß[—|[Áƒîý§Ú™Ùv§×Œ> Ø…ÁëME™~7'‹Ø¯ãËXDDDDZ•~™^ éé­ÌÎ~cØðéR�õŽQ½x}©æ½!)‚Û�`1õ×ÅJ€öœSþÁý¶‡�­Fôq~{¶ iEDDDd Q)YM�£mlyzÒœÓ@çbÇyô£L]¾±è\ý£›x1½—F# „’›'Àç ˜ôÐܲh±†Sáîö)�‹Î'""""ëŠJIk0º‡O�!ÌŽ.tˆ»áîØÍÜ “3Ûs=¸—,Ƴ>ࣜSž÷ *ÑCƦ¹ n_}’;‹ˆˆˆˆl8*%­&)w§ìæ_“ç�8àøWÔ.ÿôÁ {†wã±°ÔÝKÀ>òT"ž�Õàß[Ô®ÕsÎQ)iYÉàž@røØÒÌ7;Ì�±Ÿßx±s ƒöâ—ü[Þ>`{Î4uà¾A¡B½ø ÷Ï?Î9CDDDDä­TJZ]×ÈNÚæÌ}�” 7;Lcþ/B! PrH�ý@ȹë# æP2¦c•Wþ[Øy?•’µ¢s ƒÍmßšù0ðÙ;®zIþ³ uð)#LF÷I … ªþ^\X‘æ©”¬9£�îéãF<‹Ñ³€Ó¸W=„ ³ ñŵk/—<¦ˆˆˆˆH“TJֲ䇒Áà8oÿ,ÀŸæV‹Ä …BFõâ4ùdC‘• R²$åîàþ£Ç0ªîLÂmÅ[dçfV;žˆˆˆˆÈû¼Q×’ì©ÓÃIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/BoostHistogramPythonLogo.svg000066400000000000000000001275711516627434300274100ustar00rootroot00000000000000 image/svg+xml scikit-hep-boost-histogram-5f6f3e3/docs/_images/axis_category.png000066400000000000000000000056451516627434300252440ustar00rootroot00000000000000‰PNG  IHDRm@{MîõgAMA±� üabKGDÿ‡�Ì¿ pHYs––qFþðtIMEã 5óÝN ¥IDATxÚí�Í™¤8†ß˜g`L Æ�yT&P{Ø;eiieiiÀÀ0Lˆ=@’ü$?Ù[›Ý �E‹ ú�"BRHK–~FúͪÀ’…¶%KÚ–,Yh[²d¡mÉ’…¶% m«K?'ýþ:¢ŠCˆƒKΗ–»sO¸R’ãáQêewþ.À!²;„íÏ#¸B.ôøêõ%£/rá�à*8ÄTx»ó¿ÿŒ�>j$Æ!Ý[v"E!Dñw—=ÝH_1ò*³‘s񼒣 j}Û™Æõ°~/ýj»Dú±+÷ˆ��fœ%׿w–>"§îþ ùÜ_C¿¶AâãËßZ‚Örųó°Xëé0ÙÝÞ(YŠ·3÷zø†FöKï#½¼°_É�,g~¿ÕvàØÙRÕ³ŠžÄö·ƒ¿:`»xúõ2Z×¼ÈPÜ�y¦B"ÂÆnÝÙ®PR\<ÒýùwïIÈŽã®Éý@ïéeDv�põ¨Z§©Øû³QðÈP*²ƒtbˆH�…áKáä¡�LoLHøc¢&¾püpIŠWê³_ÚDǺ ÑÞæÎÍ Á%E�ê¹[Ã';¬ÏÎ^ )ßìFJ$é3 pôo­·––T‚mÓbæb³Ïx’J´I�„³Ö ¥¾s“L©¤OúM5ùfîÑ“Ü õ3íô\»þŒƒ³Ya>ž~6¾ºl w¹˜�a±„ôiÙ ۚϽ*õÌuãS³-"/…8�nÕüsÁ×ü©pçíú«ÿïlÀß™yΩ×,õ¡SÉùS ÙRw£üÝ^Ž~!`'\ZÀÁ4‘ÜáÖ‚Y ç�…ÿ’ð&¯8x;O]n3¨â`È÷_ÿòšô:³‘ n·h÷‰ =I$%!_º7´OÄq\<>væþI$.%qÝ�»…öáSKoó?‰‡OÉÇþÉZó!.>—ýgó´æSŸåSóƒÔ“sâËBûEIsò¹—»›9ÿ3ÙAë#¥ÿåÝHK–,´-Y²Ð¶d¡mÉ’u#Ÿ¤³Tqþø×a¾Iz¤jäþסÜÿü~X}ó2 ˆmÿòSÒø÷÷îÇùö½‘¯8…ki•ß½ÑLlbKÖ�´dÉBÛ’% mK–,´-YÚNvyÔ “8xýMâÜwؼH^¾õ:Þ#hyãà†Kj¯½ð(†[qÛ=óŠRü$ut©º?ÌèþÊöïI¯-!8 Wþ>¾»Û—éÜ2ú=ý¬KÔÛ>Usú‘xª¸„xÔä\ˆ÷Κw�ÎR.Lö먌úõ—œëòJqqˆ{z,»­z�Ë6\ËõõçâàâPs]Þâ!Õhan®'-ù£í»3§ZÊ)©¼Ï£fjk×ä˜ÉÞÃÇw÷¢�€Ÿº)É)1í¿k�ð§ïâ‘a¸’�­m›}Äá(&×Ïåî@\ |r.ÔIJ<ïÛlÄÈÛ«^vÚr͉$[‘ Å#çJN¸2±ï Þ¿²mXkÞqHž4Hgg:&=o›?#øÇÙ“Öb‚žèAöޤ?>ËáêðQ®¢¡ì$÷´8øè’–ŸÐ`Üãê¡K9¥ˆî\×Þ@t«Å­E–ž]æø!Ñú[“$†Àçm ;f›”8²oê‚`S²d·~]/\ÙgQƒKÙq͹.¦Ã8sîÌŒyõnJÆ÷Y|âÊ•àÙàŸ#‰¨¨$ÃÁWbQQI—JIEEE%»%|¯½“€T¢¢R€xío®%‘PŠŽCö4�)ƒ¡´ˆŠbï’àsé ì.³u˜ã€Dm=â»fÄk9Ä’RQ5\ÄiËV·ô<�fBñZ­§·w‰JØÕGom!î¦�ÏŸ#[|ŸÏ+Ç»ÕR<Ì’$Zë݇ ©·lsŸÏ�ž×uÁHžÍ––`ðɨºa¢ÉXçãS¯š1Cr2p0DT˜v˜òÛs 34vðQ" ŸbÍ@˜M¸˜©´8 Y+™éÎ)xx’Àl&R*‚VÚ¬gÚ¤** Ió–®l0‘+CIðI¨Ú»U3HãQ`Æ…óxø_Lš­h0¥@©ÖÒbQS­§¸Ä!""[6ˆ¹“­¦Ç’I2w3íY=qw·è¬­tjN—2X1Ÿ’#hG÷ á–\sÓ*ÎI;¶”ç•3[‡1‡í@nn U{ Gp·‹'e½׉/CÔùÉH?:gÙ.XÈÕR&C JÑ~tÅr` CFEŠ6‡°lÈx˜RQm9Œ¿¯•íßg�v@‘ ag÷\ºxɪ$®8x¸ƒÒŸd¤k�(¾¤àBI©çÞK½MÚòé:ŒÉ'ÇéT^ûæ›EZ6öiký^oT½J9H7VNRï|Qˆ«¥8ø¼m‘yÞD#X9¨#çÜd&¹RÌé]bNm²úˆLÞ—CšóÞ–Mäc5�O¾Oˆù· ÖÌ�8M î8Œ¤5g¼5°jÉ_x„¤RIx¨ [6îÖ `µX‡‡�³´»îðóZ¸û½DkÎÀÚ,�Æü�l�›ä“ëèTˆíëÜÅ‘ŸÏåî¤ok¹èrFœoj=‘mÔô‰+ëmižBœÃl"¸ß6¼êyŠ0¼ë›~èiØÃˆKÄy1Ù†ùõ¬ïúÆœ‰ŽÙ® Aë ]ú®ˆ„Ëu˜p¨¹¨ô®S×%¤’JFÐsæê�>½Ð�<>`¸Œ"õÖvi£Ðï«S+‰$›á”>H³È5: «-ï<ms�Öl…¶ß)Æ_‡‰'Ñ ’âò53ßpÖåÊô�OÔô�Zëy¨¼É»~|mBÜ*ú ·72˜bóus¸âß?>‰ÛÆtÉù$çÊ[VW¼.ά7£~áˆ�Ïå!ä·ÌYfÀ›æãì¶=z÷ ž¸ý¾sR²¼{ Û¤¬?³Y,ÛHñD:ÎtÎú–q?BÒÆbŒ‚!&Ã܈ơyÐC„R`ðp0ÄÍa”ÓEîѧ ôgø3pâ†ïj8„ Ç6’± -1>†øææµï‹pp›ØÍ\æ8Sµ�¸{ÞCÛ˜¾«EÖò g"7îÄ5,¦Ž4î£xÇdÊÆ§êôhHGî´R�&W"¥‰óÜÝÈIIÿæ>â‘ Ïðœ”M¨¶•]t�OÙ$K'5Ì�ëS´á3¯erûÏû™á@-ÁÃ…:JBx?p´ãæá�^}ƒUÚ•)†~õø]x#Þ²´8¤-_oô1ŽÞ6­Ã<‡¶Dïy‚Þ½„ŠCŒ¢whŽj�<8¹'œ9¨4X�v5z(™´�y ÿ”ôÛÚêø€×E®ÙZÙ¦ôVh¯ÌnÚt=õív/nx¸Ä�›l#'wöÞh–ˆˆhË�JS™î#çjIw:ÂÌ–õ¶–Å–kcâÑÆ$kqr é£ÖxdE›}8 €8^iq‹Ó$¦Qª-ka¶—<¢,N/öäîÇk‘…áQk©‡Ãrp·ìŸ8ñ+|4½µÓ¨’n;*o{ÉãÊþäg´¿IHÔ�µàãpzv ˜T”8”únµùC-`¡}˜j]|<jr¾žßh!>ùm†Ð’…¶%Kg#-Y²Ð¶dÉBÛ’% mK–,´-Y²Ð¶d¡mÉÒKÒ!þE¸Ãù¸%tEXtdate:create2019-11-22T16:53:24+00:00$ë1%tEXtdate:modify2019-11-22T16:53:24+00:00U¶¡�tEXtpdf:VersionPDF-1.5S6ZÁIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/axis_circular.png000066400000000000000000000125311516627434300252230ustar00rootroot00000000000000‰PNG  IHDRäÉXâ‰.gAMA±� üabKGDÿ‡�Ì¿ pHYs––qFþðtIMEã 5óÝNYIDATxÚí�=ŽëLv†Ÿúð“N}NÆ»‚oæÀ€Q½�؉ ‡ì%HK 0+ bGì ˆð ÄÙ�˜NÅI0¦�b‹)QݺÙ÷}4ºÕT±X§^žS?¬2!ÄÔùEE „„,„��…²BBBBBHÈBˆûñ«Š`º˜˜€…/¯žk™a ÈYúBe'!‹±È8¥ðs0![óý²”�%eî cIØšŸ«ü¾XmÐÌ®Ézã™ÿVÿž`ýÛÑÏ+©ËŽòðM¡6²x.Ž÷9'6öâÙ;ø’ŒÀ8 „,Æ@Ä{x\áų‹žß…„,žXÛ}ÿÝÿÖtq¹w1›Äì�o+•îQg×4 ?üYT¢6 –W2B —¿–�Å4HÉýÀ8J?3cís’©/[¡µ˜FHž`y©ÃíÌ/*ïîóæ§�G�Ãgæô£r€Œc¬ÿ~jW*UydñxJÚ^Wý©‰«Ñf˜wÙÆõ7C©„,Ïúȇ:JŸoË„¬X×X\3'¬™fa'ç¿�¿ñòøyW¾ôßYò Gs¾$dñ)¯8ãÏ”¸g½©äßXò;VFKÜk®µø°y"VÀë3ßPªìÜ—=ä‘ÅÇÚÇ)°x¾ä…’Ф²ˆ„,>â WX–~þôvz%åØÄ²ŠBkq«i¶„伌ca“/>“eä‘Åpá$„”c‘1ø% P§—„,n «càuLËäù9–™¬#!‹¡$XÖ£ c@l™Gmd1Ä(;à›/F—³”X³¼ä‘ÅP ËñÉX>YBýž#¢d1Ƽù‚eý ²¸Èl´þ`NIdœÌ$!‹ËþØ�Õø’eý°²è%£Þ�iA‰S;YB—ˆ`Ü/ ú’u�O!!‹ÎÀ:‚ ì–˜IȲ¸„F¿©š_Sj²¦„,.ÖSØQ>YB½�uˆ%íÀÓ©�5%!‹^<�××@¤àZBS¬=×w÷ÉF yò�u@@ñ¬Eön&‡f{õû¡‰&äWÁh€b2¹ýB6î0ôf,3,ùˆ'«Ê#‹NÂZÓò½gwEU�±¤,ý¯X¶&TÕ��§Ä¤Z‡¾üBëõBÞ|¾d¬T5$ä©…ÖSZÚ.ƒ{θ6qs÷»*e_’è]+ ùá˜Äì�oC=ŠʉÝî=}²k昷ŠÕB„:»î,c,¯dM›·\‘ñù„n6Ç�!LB|ÒxXû×£ÿ[‚C·VkO '1KÈ�–±£ôs03Ö>7!Ù¤„ùÛõpq׺ Y¨ßZB~(>«ýSèÍÏîjýUø7óÏŸþ—ÿË͸ˆ—Ž”Ròçï´!!ÿœ~9¸Ú~ü:ÓþÈ;?ÿËm8Pœ¿¼il§¼…„üâz4¸üsñeäý¿üO—G¾ùŸ·µMŒÕ�ò3…ür¨ºÝ­»î`ÑLQÈÿqCà{éçN%k"Âjýl€ZÉ×ÐðÓ½똲i~ýñÏ[†Ë¯{:úlÜéŒ6dŒÞ{–�ŸBÔô¾æ7 9ƒIMG¼iJéÅ\Ôî±6!+0‰ILbRbùc…Ö�gÞ «¼Þ8½£dbÓ4¹e„·ý€k//žlB³"8ê1ÐF®òãyX¹Ù�€›Pµu7ÝcïΜutùoªGòt™Tiìm9¾ð€sh¤Xmä/&äé´‘Ãû‡�ïJÈbòÁõ$ö¨’�ų˜ÄžJ&ÂN`ÏH Y<-¸žÆ>‡òDzà“ãQûc;�­m$dñ,–ÀlÔ>y†%So³„,.×v¼«…KLß'BB í T‚eí5ô4¶¬WŒÏ()1Ë“wtÇ‘³€ðM�µ<²˜²ON@û#JÈbX;¹`Y‹f\þØQêía Y eNI4ºÍËfÀRA$d1Ô'—,�Õ˜†¡L‚£Tµ„,n‘òœ Ëf,R613àUþXB·ñJA8Ž–² I�7 ;IÈâöðú•’øù[¾ËXz­å1Z4Žu%¯„�‚WYCY|ÆDÿÉŸùî9›¦›”˜¿óO_~ËvydñC…ò/ÀïÙ˜‡¿Üh¬Ùó~7î+…„<~oø=K–XÒÇv{™�-!%ÿNIlRYcÜ­0£=ÙãI=b<žöA׎ÙãÙbÛùÐ1Òº¢"˜‚Œ=\-­à×Nð­kKʲŽ{ȸþl‹gOüC¯°Âã™]Ë� YÇÍ2öx°µÈ¶¸r][ûâ=ÑÐ<é��uÜ$ã¦õºÃãÙÞ]Äû*¤îß%e YÇ-‚º*—k’ûPçÖ€ÇC-噬$!ë"ªt�ÿ¬~M>f0kDìD ›Gõœë~hf×”G™fõT�’5Ùm/šGTo®^°ÐKš¢)ž'æÇa.öšlÈ.‰ÆáˆêmÕKÖd^ ÎKÈbrŽ�v�Ê()([[‘[B $à°¬_Áš\–�ŸŒi‰p8®¯*’‘³Ö‹²·YC,�?ñþÊž‚’‚Bû(JÈbŠ&ÞàxÑ"=_½ý$„„,„��…²BBBBBHÈB Y!! ñÅøUEðåÉMÊüâhЦ ­…²BBBHÈBHÈB Y!!‹Ïa"cU ²˜:3Á×B3»¦ìWm#È¥/nøž{_€ï£i YÜOÆ _‚™±3¯7¬O‘}: 1.´kÎdwˆJð‡ÍOñloøæöóièס6òt)¡Ù3âO“}6 ¡Î.q¯Xjá�_˜¾nµcùé4„„,îÎŒœEãq³7¾u¬Z-ë ³S«•†Pg—xd‡—ÃRðÒôB'X^ È©ÞF>nÌúZb‚5Aï#OÞ„ –W_‚q„~fFæóêçÉÙÛ.Á§!$dñ#ZvþûÑ'+ÿzøy*ØÓÏúÒj#‹‡âKrBɵâ¼?:îëÐ:MCHÈâ1~xkv­Óá‘\ó“O8¿˜†��ÅC Z[šçç~×´|²qäƒÓ²xk^ª�$cqä‡é•&¦lº¸\ëÑi�u_brñ™:»&k:KBN$”ï=ÎfCîçUàLqܵe¶§�Y}i Y<Ò|!Ëúx�É„• M@ù.MT¿ž†��ÅXM½b®ÕFSÄÉXBSçlj¦��Å�Þи§gâè�'1Á:äÔF~¶ BV”ÏžühXO¹l/¿Ör&d3Ãóvf÷§w¬ê1ó� }׬8•Sø;úœúŽ¡ðoŸ¼· ë÷»3–!ëëù5!3r×dؤ›“ù§ùÞ�[ò±éÞ˺g¹LN>*ïÕÁh,è—òyh]’ãˆ}z/fÄ|4ø,p䄤f{ÇE^Krr âO†Å)¹;’ñ†ˆœ59‰Ù^}Lm�Œ€­ ¯<±wuº%©IŸæ:>cÉG¦{/붉puºU�̉îµþŠ/yÁ²ê?¡kM'O2ôÓ»¬>e‰?ü]‡Çy<„ìI&õ~†'8ú;e‡­ñÌ.–Js?lØ\¼Îê(Ý\ž¬¶ŽØ',ùøt?kÝ®UÔNêä}Ó¿�ßQtvùòÁ ÏYÞ3û€EÚã“EfƒÃd ð9ÙÅçu„å0�cŽ»è;4é®ÉZÞ+ š–%—î�Ys:áfÁ{&|FF<4´®°fU-ÓéLj¼ñfsYÆšM½ÈÌöðbœ ß—�©—¢Ù� ›ei\» cvM ÛÁwj9jA4KÞ¤ï¹5a�³Ôlس7Q}GQ“ÃÕmwÖÜ×Ì„u™mŽÚLÁÉkƒ9á!7&Ä]\%Ë‘‰¾¸Z¾�T! `b³9ØÏ8³1QcÁ¸mÉú¯Y·Õ;jÀÙ™}–¼±%™m�Ê!gé^,óV ŸµnßµŒ5‰Ù´Ž|îOKòs_ô¤p!g]5·&ë}(÷„ÖžŽˆ-ûChˆÇ³!"ºÂâð¤8«&Ȱ8ö¸Ã"¬„õ¹×Û‰ð$8»*\¹n`™§Â†=q�¶ 2÷lpÄìÙãX®ŽÅ‘¶CØÓ`æüΰ8[¶xVD¬Ø·îÞ…_{Rö—kûV™\ ®OBò¤Þ7x6xVx6XO}¿'–l>=³úYê½gvYòÆÐ1nJ÷x™Þ³tûËü4…ÏZ·ïZlNŽU_º½)ô䬫æ^®û„¼i2±kÚiž]ÓÒÛ\�æ-¶mëW¤Xv§-ž!'G+/Ï.¯¶\W«êH[U"<:#jDd›ÿçW'¹lê¾;cÓU�m¦“ïoÙ×â .ÞY»LÒÁBN�eHŒÇWУ”;,Ùmõáõã³=)ï ªr_ë¡9/óî>oÝ>ûÞÒ–íJ¡+gÝ5÷º�ûßËm³fÆ[Ó8ôù]�æM`b,!Aëì7¶l®�,‰ÍŽ5…_ XÛqNN@Œ3¶F#rާa„uÞ-Ô¢j¯~¤�ÒsgEÇ g雘”y½mÂÖ¼ ÐØûiRbÞír3cF‰R³8*õnKv[}xýølŸ¯=LZñå ¥yOËüö†Z÷äZGí4ýà~qcnûJ¡»æ^áú*šÙí~KJTwÀíN ³ åõJqæ[\¦dqµˆrŸ�ÉØ5ó—B6 �óQúºÿÎ:_É/ ¾Þ�äýnüÜ„$¼\(ù ÕêÍä.!âí X“0cAΊo$¤†Á]FížÝmC¸nsÍ?�ÂPëž^ËcO´7æ¶�®š{Ü÷‘LÈéºHp¼Tø¤³, aÁŠïWÖ]å‰�%&1ùÑù¼û­’uÇ"s°1–ðƒ^¤÷κ¼»ÏLyÒ¡Õ–c~Qù{¥ ¯M±4– Ç>~ÁÒ¦êÑ~3Ë"÷μ>#€&¢ü�O¦0Ôº'×ò¿}äþ�p¯ýë…Ò.ºïï—^¨æ™¤uãËé­Xø9Å•) IµH�/ý‚ü¸²œ]ël…‰«Þ½£^ÎÔÄu¾rÞÈÉøÖã›®°ýwÖ7 �3jÝçúÂ�-±fÖLˆ(/ž‹ Ùß|&hJ¯xŸ}~µŠwZ½³Ä?W?úʪ|ŸeRs»§¿–½­{?‚ã~鎚{µ´û12ÉQÛhΪz/Ø„‡²mÝYüþ¤÷…Y03¥_š1o-žœk"R u^gð²ûÜüÖŠ‰ú-ybõëvžyÁ’g¹í+6sRSµrÝÁf]éÖŸ�•yw Ÿ²nUoûíÛéJêtë—úrÛ�³žš[‘Rö>6{>Rvõ EX÷6þ7¿ÍZ{úÅg;z<;vxVÌêO’£ÔB¢“Kz—7Í9»“~»ÖµšTgGEÍ™»v Äì�’ÚõÎL©Ýq“ïÎ;Û´ÎZu©Ä­¡µÝéõ[)»ŽÏög½ü'ç¶î«3Gã=–ijj[½×ºgv[²ÿÎzr±mßqWº„ý÷Ú‘Âg¬»ºt­Î;x?öÉI ]9뮹õÃ…2üj[�†ýC:­G@@ú>âûCóŸ “¸®|Ÿçû>$ríÜOæsðËíCL÷ÏíO·‰îêôqú¥…|eÞóöìæÝC®ì<Ém5UdàˆëàsŸ/ä“ÛŸLÆ–´¿ýÌû#g„ÇÓMLù˜uœ}æ_L/ Y i¼íÜç3­ÜŽ_ú·Ë3~ª…ÌŒ„¼Š°Ì½VÍ8”Œ«_½{»Ü·=üLñH~ªmUý¬‰±”,Yª"õŸæ£“ágŠG>еÔ�ÓG‹ï !! !$d!„„,„��…��…²âŽü?ؤ�"qèTA%tEXtdate:create2019-11-22T16:53:24+00:00$ë1%tEXtdate:modify2019-11-22T16:53:24+00:00U¶¡�tEXtpdf:VersionPDF-1.5S6ZÁIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/axis_integer.png000066400000000000000000000042451516627434300250570ustar00rootroot00000000000000‰PNG  IHDRð@Ž–2lgAMA±� üabKGDÿ‡�Ì¿ pHYs––qFþðtIMEã 5óÝN¥IDATxÚíœí‘㸆ŸÞÚpNàŠël^˜¸e'@…À � �“€]TT¤�€Ér¦Bhÿ¿¥á‡V¼šÙCëǨ8PÝ/Ðx¢!Š“ŸY¾88€�8€�8€�8€�8€�8€� åë¶ââpÔË> C¤¯{iÆàQð¦Õ.Ú¯²ƒö®K¤/›~¢>$DŠ‚O‰QžûÁ#&!GŸ­YQ0¤x †„ÿéÚã«GˆP‚=,P0”d³¡hHÙ}�Ivh¾Uˆw8i»$†º·äIÚãV”|'€cò­o™ƒ-}è)Å<7üèEÏ;NF¥x×z8ã‰}ªö 4“×n">›'Æ/›\TŒ ò?Û¨ÞùþœÎyTÑcìÓQ}Û‘E߯ö3á«¿é/ù±{€ÜHDÁq‡ññöÈïÖ³èÏ5^ç\åásÜ�G[,>/Ï_aˆ�µø�¸N(öXˆéY_9`HŸÍN€@Œ 8€%Æð²—v­8à“}ŒðÜ,™Äkúç]æ¸Â¡’íW(™ø[Êcô·µ!T|É$ܺ À—`•þUž4<_=ÿuYº0=Åʹ{O2æaY%À×C3¯qšÁ®³Rr ÃŽãsZµð\#ÓuK%1Õ*>íc·mUž+=˾«Ö}Ö‘ ¼@° €õâfÓŠç©Ös¿ì’ˆjØð¹˜¦—ú�Ï k–d»ðДS3 VŸ»”9µ;Ðb°zú(ÝzÀZÈ�T\ØÜ�V‚�5c!“ËÓ™nŠ×½xþVÄ�X<* æÌ÷]ºý•Ëø’mñͦ·Iú&g,'-ö0A_öëÉúm×m” ñ±ûøæQïl|]¨Õž¡ùs‹¯Uî…ÿO.`°°“�»úWDùÿÞ­†¿ð'öÛù3¿ò/ê�´ÿÂ_ù/ÿÙ­õvoÏ«ˆ"ÿào®§ÿ”òOý»(b~žw½NFRèE\ú¨#YNÀNÀNÀNÀŸXÄ<ó°ýZ]àß ^ŸœøiÚÍrnõ^'–Gå¼8a.䆧Xrê6Ä (AffH‚Å’¶¡ƒ%¦nS½ ®¹öX,vþ Pb,–€rMZø8dáao[‹ÁbÉ›–Ù>ןŒš°©-ïtÔdXBjj,é5Íû¶l§7GI H©vŒîC Ù9¤±(%JÝ'±¯ÒóÿÊW$ÝӲ˙ÏÞfÕ礃ç) †r:çNŽûLy¢5Yó·æ¾×ÚÛ9˜mÁƶùúÄÔÍÕ a?�Þ/Ûè½á(·wLìL. ±„”ä“®²ðü©Ê¢�åDD›pê8õâ!ž„|¼Qé9ç…9÷�PJNTTz|8çvmk †[mòIÕÓ ð~þ^Ùkù×~?/ÞÇöÊ9SnóÄ×Õ±>ÚNIH:Šï�é”I–އk%ßš0èÉ…£wæP÷²+|±z‚Q²Îû„Å4:zoýaæŽÛꥣch%gì`ÃBšÌZ€¡1–—«a’æs$å·…Í{¹b‰¥Ø#JBNz.œôû�Ž ™œ1ø]{¯ì;ãUÏrÁŽhVÑs_ñæÓ€$ý>ƒK5¿Õñe!lµc1X>ð.¾Ä# =ÞÞ�$娯T’,Էצè‘bØÉnêz|G7iγœ ú +I“6êQp àÌ·Á„r¿ìÜ4L&#Q·½s&ÂÝXæ÷£]¼qGY…ËÿJ†,ºá¥ VÁ’�cû<:¢RqÃ4-1J‰ÅÇ`I(ñ‚a;Ú³nÓ]M”tìˆ$�ëºjˆ®Æ>ÓZj,IK¢šgWÖžt$ËG›ØÑVÅMÙ ;÷&7€ÕcêuõM³N¶`´åß�bŒreìC’Eº|_ÙÜ¿jÊf¡á7 ۆݷhÔ”ðæö(E)‰)QR¢æI<ÐæLªnlÖ•)Ç—‹MëŸhðç[‹!kôú#­å¸6Bê�Öl°¨š”�´ ½ÃÑïù¦¾YI\ŸÛIgÐf¸™Q'[±o‡ºÏÜÝ{XðH†ck£®pº€ÂôËÈIÉúÎbiR–tÚeÀ�ì!ç7î²?°Sž®º±^Þ'Ç�¬»ŽÑÁ8ç,Ã~ÆÞ-fdý¤÷Œ�;6;ϲ#bІ¹^õ“¥Ï:€—�ø.¼íu‰²ØÉƒâÎd9€�8€�8€�8€�8€�8€�Œäÿ9b ˆG¤›%tEXtdate:create2019-11-22T16:53:24+00:00$ë1%tEXtdate:modify2019-11-22T16:53:24+00:00U¶¡�tEXtpdf:VersionPDF-1.5S6ZÁIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/axis_regular.png000066400000000000000000000042441516627434300250620ustar00rootroot00000000000000‰PNG  IHDR LÐvZ¹gAMA±� üabKGDÿ‡�Ì¿ pHYs––qFþðtIMEã 5óÝN¤IDATxÚí�Ý•³6†ŸÉÙ”øÒ@)—€s‘{¶¶\”€K€r%˜L9G¹0Æ`óëÝMöcçåÃXÒH/£‘#±(÷øE«@¡´P(-J …ÒB¡´P(-_/ã·$Àà`ë­©=­›¤äT”¸¸Tö¸9í ¡}›±#1¡Å‚Ë cÙÒ1§[ç4Ú˜æ1vFnärÀ©=�ˆ7U5³ºQál‹ ž…èYZ¤dí¹�Ý’½˜×ív›Ç<-Æ\NŸ²=¯wC]ë–uûL—SÌÃ%�|3îÖÝÄÃÅP“lÏÝ~~€ºåçÇ]$ãØƒ}£¦Gi¡¸`o›P+-—Aû­SÉñ¾£½øe¦Zº®ÙÖš|D7ñÄë]ø†´›å¬é»få†tžÓ-Å :±óŒxÔ6ß�΃º‰CÝŒ:ÊÎÈÄÙ˜îïô-¼v(ç’lJçÝÄáDÑZ�¼Ìz¶Ùðõîݱ%¾àóJMˆ±¯«’ÝÄPPÚ}s?¢¢Â’ϼRúùtÏCIM9¦�Œ¯ü‡£-7ø¬Ìè&.>¹­¾§o!úA€Bç-J …ÒB¡´P(-_‹I¶bPãJ&ÑšÙÉdÅ+|ÉV—&X!­.ÍZ]W–æÿ¬ù—Éÿ»x+(fVIƒƒ‡Y!¿.uƒ·êMŽû©¥qž(ÍÿXóÚ‰(” ¥…Bi¡PZ(” ¥…Bi¡PZ(~"¼Ì ¬˜„ýø}…üoÀAÎkŠ»²4®˜Îþ}uiÖ꺲4Ÿ\óÓõ<µ:K‚ïùMÕ7Àßü5µ qfÑžxZƒ›D5½JU×r*ÔåT(-J …ÒB¡´øù!æk�èæK£´øüFp)ˆ¾PyR)dÝ¢= %{\:|õã*Nb1Ïñ^²æˆÖ,¡]�rؤ¿›9»»«±øu�J!ñ’Ø;kds³?€lªÆ­EM‰‡¿èêG!$àY3[á5�˜ã¹g`jJJ*‚wv1¥}íÇ럠ÚM< Ç¡˜{øÖÈçfkvÒ‰?�Äp�–^ý Ø²ÁÓÿõ°xM¨æóGǾ¥þäÿCl?0.ç~]bn%'›»Fv8·%š} Z|TÃq^EFóé´8Ý•”ÿŽÁ-–0îtŽkd‡s»Å*&]Å×H*V¬¤}3%±X±’ÍÄR1’‰+VŠk¯&ns%9‹+'·9·}S-¡œÚŠÅÛtãIÔ¤Ýñ[ÄmJKÆ™³ø�F~[Ât�f­^¡¸M�e¿Â¹k÷ö�‡=<òk7cKªÉu�ìpnWäãNÁ-`LJ¬ãØ„8ìÙãÎxÖ. ;vT¤MƒWì8P“ °g¶dÏŽ½ÏqÄ'âÈŽ{̲à±b$$ ­ÉxcÇ�¬õÁ3`Ç+>.;ŽÀ�— ®;’™Oˆ5«Ø±£Ä§ö;åu`Qôãþ§EÕ¤Ækd§QN UG:‘¬5´­Ä^#ëÏöi^k芮©"%Æpº÷%îŒjDÑé�‹Y3=âž©u;þµ“¹”‹ Ûçßr'êkö؉ kF6Ø�†\¿Ó·ÿk²v×ÈÎ8äË8“.>« ¹ÆÎ:¶^ú l.Ž\œžô+¹�Ò–ȉ#•=,jöF‰C€'¦1°>%ÝI$·){Õܯ‡'B �jV Ä¡z.Ä’óI²:ËùDøA1’r"À¡ìSÈÖpçÚVü Á%$“³„óæÐæ6a�Óé-]²öè˜Ü†*þs�Ç5ü´Z¸b%ï5¯™¤ÓÙ9Ol4ÞêË‚~o="¯ÇyÏàâeßžS‰›P‡’WJr~ŒtcsFy\³aM&˜Ö&†Ô'ô]#;i÷¦ZvÌZ8sÄb8€88€Ïæâa0âu~Ex8컌–Š ðð¨Ä£¦ÂÅÇp2©(m.·qÆÅ([þ¦r <\Þîž×6¯æÿ®\ÊR!�ØD\RIÈŸ ‘¯q©ÉGè,[«pÄ<|Ê‹f�Æ—Ô/¾C5¨Ù?¼@S#�‹áøu½Ž&ÝFß Ål%B©m"!ý9Ñ�€_;6q±ìpníìk=ñÀz®gbNX,ÙÅŸ'moíYØõ¸ûc ¢æäDÄ KJxÛ®MÍÅ¿Ëú:bÈZ™ÓåÚp^mªaç—ßJžú)pî$•uÆ*WÉøšZGãÞfvše=©tpú)è�ź·Í]��Æh—ëÞÀµ²£¹Å3“fÛ…Ëßv§G(‡x͜軶Ì[4?Œ¹ ~ïþ~�ìàܧ�~ݰé­ÚÞOÊ{ÿÑ}úôÛ¢ˆó²—Ke1ÄW;©´xxƒ@p�ÜúÒû–f·ÎúãdçŽoõA€„DmüÛMP ÕÔ÷úNDüfÏÂò»îZ¨´P|Úä·Bi¡P(-J …ÒB¡´P(-ÿþWÔ´:ë„ãIÙ¯ûc‹OÝ:àuVºòïéw¨—�”ÿ乿Œ—µU… @ÏÅ:²çæ†ÇÆGÏR\[ÉX^ù ß£´Ô�§âå‹Õ⋾YIR!$ÃÇ{vArll<*Òö8!Äá(È–‹ø–~—ºBLˆcGñjµr…ŒŒ#J±å‚#[þM|>Z>ßL�-o-wSô%`Ç�Ür˜+ïŸýJÒ`yLƒÁÓ`Ši0˜bL1 SLƒáiŠ)*_¦j,n[nDÿroÛÏÕVLƒ™rƒÁÓ`Ši0˜bL1 SLƒÁÓ`Ši0˜b^W–ìù×…Êw^1Ù |Ql|gmI3˜�i0˜bL1 SLƒ)¦ÁðYø×DðY� »œx—Í_e¸Î>¡ewCqq“3Tû|ÎÖTU—9âö°z™fUÇöwu¾¸¿•Ù�)—H2É$˜söaÏU ɺ}ÐÅI&™¤â�ø’J&™¸­x¸+›L’%£‘‘ß&©UTTôG‡«T IÖn–-�$’I!™DkÖôF¢ æ'Éewf=èÿTT®ç©~Ù=yßú˜5%ŽÝ¬³�BDøíV.í j‡•›jÏáa\6%Â�DB©¦ºeˆ£r|Š5‹‚ød”ä”D+~!zÚ±®l>3ä*;Â9»3kÍ/<Ò…¦|¸âv}ñW_ë.f‚pýv¢sy“B—�IzÛÌïº Çv÷ÍŒlåöªn9kæ¤s’Ï0�ûÔ¿DT®õ©ùȪks.  ‚¾‹ý,#œÕñw‡Ç¹Ãín…‹²gßnâ½ý>�Ž?‹î\N¸4]äI***iß„H"**Ù}oL<ÉNÎ’g�C‚æL r• $h޵/v‰¤j)3Ì[ãáÎ{Ÿßå ’ ™]ÖœàaˆÂ}Ù È!n(÷¼W ðg5 täçÇLKË�­õ­}P#êm4èoý¹°v~Ç9Ýÿ:m¶j>ž�Š’±cw11wL@‚Ñž—k<1ÇsïVv§}Êq8\ß°C‰q8vTS}^‰NßÀïtÔãÀQP ¤ìH9¶ß¾åa€Â¸l† ÇfÃë^üÍnØá¸1åÇ?«ŒyÓ;¦@—»K8”Œ åH2ÏIšoÊïó±“Yggñ¤=[µÝº³É)ymƒä´s>%Á£ºÒ•RÄ—I4µu?Áiz}s”ƒlÔK¼:?LaX6·B&DÛ®Û}¬¥ò�böe’¬TÌ Ù3ý¸¢+»C©q„TS žÇ*æX‚½<ù j=‡�6�ôÄ—�¿÷í?däþÜ;¡T|pà û©¤ZJ�#ï{˜£ÀAßæù@#†esë!–xç h¥·ÎÛ[™0Òò”¬‘˜T~/4µ%{ö'™JNE¸¬ì¶%É5ñ$¥"Ä¿N2hÍž`RÕüà�€ˆLŽ32p'/Ó]x瀙=}îR˜'›ÓZuúôŒ^EäÞ¬ºÊèý]ø¢oä,Ìev}T=�?¼ƒ²7~7¦K’k˜‰qüjÚ2W™µ˜=)?'ÒÏî´RŠGH,åD«$¿3ÉQf¯X÷(Ì‘M͇þY‰ëîCtgVaGn‹³’ölS¹T1%…á9<ŽÃXåŸQCt®ßî¦"�Ľ›çó>¢J){}ã0QEØŸjZëž²«7c�W®¨·‚�s0ßtŽQ˜'›œÝe]”Dº‰‘�Y¥Šw¼ÖZDÔ|LHáöì®—ìv‹)—5[üþÃ4|ý2»zW~£QÒ�<ñÏQª‚#¡Àu"Ò íq�:b” G€‡#¡ÂoœÞ×FÊ—Økû–‚» ^ÒŽ�{‘ÉïÄ­Ã<ÜP¸#GtšÓ…2 Ç&·�tƒŸ¦VO?gš„|xñë9§0.RŽ„ø )Çs06—1JŒ§œ2 ÝàçöúF�g$ØSªñpjøä‘„ªI ‘ó?Ãö(êM |!¡"¦BI‰š3q‡ZÀîjè]suÖ~§êG“·cµüŽKÒ^ú憇! “²igØR¨†æÐÄìá”b¶<‡¾{{³ïÃù5Š¢«–s)t®¿JÝ^%ï¾b’ܯ*½Ê»:ÞÒrÛ§s>J11q·û$Þ%muuýqvÂÈÝ@çS¸X‘¥×79ÔøV I§òª/ò>¦Ö|³7õ]W¹œ ñ ëû3�ˆpn04ÔTp …!O{öõA;·Cïꚟ÷[%ÚÏw¿!Ä'!×ýçQø{&G0Å4|EØo~ ¦˜ƒ)¦ÁÓ`0Å4˜b ¦˜Cÿlÿ(9ÙT9 %tEXtdate:create2019-11-22T16:53:25+00:00‚œ…%tEXtdate:modify2019-11-22T16:53:25+00:00óÁª9tEXtpdf:VersionPDF-1.5S6ZÁIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/banner.gif000066400000000000000000002652231516627434300236310ustar00rootroot00000000000000GIF89a±÷'''777GGGXXXhhhwwwI¥wSª~44²HHºSS¾^^ÂhhÆxxÌY­ƒ_°‡d³‹n¸’x¼šj¦µt¬¹}±¾‡‡‡———€³¿§§§···†Ã¥‘É­šÍ³¦Ó¼��χ‡ÑššÙˆ¸Ã’¾È¥¥Ý®®à¸¸äšÃÌ ÆÏ®×¸ÜɧËÓ¶ÓÚÇÇÇÀÙß×××ÇãÕÒéÝÈÈêÅÝâÔÔîÛÛñÍáæØèëçèèæòìåìóêóôÿÿÿ888xxxJ¦xT«IIº\\Áf´�m·’v»˜u¬ºÀŸ˜˜˜’É­™Í³ŸÐ·¥Ò»ˆˆÒ™™Ø‰¸Ä­ÖÁ¶ÛȧÊÓÇÇêÆÝâçèéåòëêòô%%%555EEEfffvvvTT¾[[ÁiiÆww˹¹¹€€Ï‰‰Ò––×··ãÈÈÈØØØÉÉëÜÜñéééééö&&&JJºww̆††——×ÓÓîæææHHHWWWggg]]ÁY­‚c²Šl·‘k§µ¨¨¨¶¶¶‡Ä¥ŸÏ·¤Ò»˜˜Ø¦¦Ý¼ÞͶÔÚÆÆÆÒèÝÉÉêÜÜòØçëâñêæìô!ÿ NETSCAPE2.0!ù,ÿ!þ Created with ezgif.com GIF maker,±ÿ@ŸpH,�ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýV=6Nœ°ÑÃHç ôǰ¡ÃV'(˜8ÑD‘ DÌA¡€ÁÃ� Cr1qÁƒ„ BR£ŽEÊœIS�‰#†ô1Å�ÿ–;ÖJ´è Aê`ˆ‰åœj´ªÕ«j&Ò0ÂQAž©1±ŠKK�‰7 Ž‘Ð Cܽ1â�[‰Øa7Š„zßAÁ÷�_ÀDÀ"á1"Ä]< „»EÞ!4øâMÒƒðÉ'Þ°ŒÄF]Ã#"ûèìóÈ�§�U—�M;–ÄÖJd‚Q„ ŠD‚—…íj\AÎ"Š‹Ü@Ú\¸“ÞI~7`i\©)�[ÔnÄuã"N4r£¸qëµãË?…4l B02˜(B`q=P÷À „¥”\o$)ÐÀ(œ0Â~ 8€LEÜ4T(4FnJàwÿyBü–a8QÜ#8‚€¿5Es0¸âD XX!E1fè^róõè£&7ÐÈãE»)WRZCܰß~ Èæ (HF)q(O¾H•6Lä’Cð@˜I`‡D‹CHôœ<ÙW\zE  ‘DÛÅE#™>Ü%„zyéä“cþ(è •LEQCúà!F ia£åiÅExbVÑÑ ±_�GD¹f~(åjh…éÀ u•P‰Ä’HD¹j‚«AÒ§BPíD¸q¤J„úݨ>LtB±*!"§]UjçzÓ*ðÿ§µ’öêî»… àÞ­“ºTl–G0…„nö«ÄµyIëƒnÞÝ—ÔÚ¢H-`¥­*`.›AðÅY|pnÃëñÇ}ìäe½“®tFARÁ çëÜ´aI• &Œ@óÍ4ëfŸ³Än+kÄ †–ØqL�Ä©šKˆ8Ä~3ãì´Î G-5"æjËF¦Ì]Á$ »o‘™6'6EÐ&ÁovÜæéÞ߆˜6ô®úóЃ�”Ö{¸ò‚/Ï5µJt)1ÌCð$+ RúÛ¹¢Oĥׂ~ĵ²½DJáã›DƒG¯ÿþW=ës×Óž‡…;#Ø S[ ’f©ï)ÁB®»V�Ô,†½l`Çq�ŠÃ)Î)´‚( å�(T�÷g($�g–ÛÚ¤.Å3l)nKŠšˆùˆp‰Ä Áž‘ð%¾"ÜF„^*ž¡ÒVœ6�y9\€EèÃZ…oZÇhw/!fm€9ŒR„œ¤§!Ð> `z`(Ì�/#kÔ �¸dF6qÿO†rxPœãYKŒlÑ –”¦@"f\WL¤þz F9 %Ò˜½<¤²þ¤c½¡AJà€GR¤‚ÂyÐÉàïk^*¥‚– 8B $['‰4ý18h¤ n²3 –D•:¥"‡)5‘Ç}9ìWà.ùÃ/i%|lÎ*ÊÁ4r" @fÀcœô€s"*I3W�´pSrÕV¦âFÅ]³#Ú$¦<÷Æ ¡ î9…zîó„L�e„¹šÑd n±Œód³05ˆ`BYêI …Îó¢•Ö>�´àIdœ ©Hµ°QO`iŠÃZAGÊÒ–6�’ÿ£ ö¦CC—Úô¦MHœ)ÎÀ“¤¤8 ªPMà †é J…ÊÔ¦¦ÂŸuªT§JÕªZõªXͪV·ÊÕ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p�«\çJ׺Úõ®xÍ«^÷Ê×¾úõ¯€ ¬`KØÂö°ˆM¬bËØÆ:ö±��¬d'KÙÊZö²˜Í¬f7ËÙÎzö³  ­hGKÚÒšö´¨M­jWËÚÖºöµ°�­lgKÛÚÚö¶¸Í­nwËÛÞúö·À ®p‡KÜâ÷¸ÈM®r—ËÜæ:÷¹Ð�®t§KÝêZ÷ºØÍ®v·ËÝîz÷»à ¯xÇKÞòš÷¼èMÿ¯z×ËÞöº÷½ð�¯|çKßúÖãýÐA ì+׿—(ð €OȺ!`+¡Áü=†þû ì× 1ØÀì`á+èàxÂl…C^B†7ábô +¥Œa˘ÆV±ô{…#6NB Üb‘€X²|Œ›�ÉÃ2¤\ä`dXÖAt€å ðA @€ ¡XFòl€0�dnó°Œ-ëàË•`€ÐÙÌR�r @0oÐ…®3àLÚc>ðb€d=c€Ï~Æò££âÚ8>3�ÿŒ.Ô/вöl:c`ÓD`´£‘àéB›ùY®1PiÀB 0 àlzØs�ìë žfó³‰é£8ÐFtò\)o ÎÓ®²-®|éJëÀ8 îd€À/š ðå2g¸x¶¼�{ç»WÀ‡Àë_¯úÒ­þó€ o ¼Îñ.Â��‡OÜÜg>p¿×üo"l É~�†�ìV»ãPÀ�ì PÜtÖñb€¼àá ðÁù|r#è@ã"Ç÷´/]q_˜Šö® P˜@°_0GSºÑÂ>@º3àÿr ê�‚�°qÀÚùȱíò¢ 8ò À�D=Üâ¶Å²�@p] Ò0€ž x"d€ÈB wRl„ª ت”«@óDÞðÌá»ö!làAöAć0a#L¾ @¼˜enÖ¡ÕE@¨Oo„Ï{<ÈìF º¹íqô]—?Bç}0a£[ÕG¾B `„ÑŸäN¨üåáMm�ºäMÏ».öÞü„èÀÆßÖ3ó`ªÁØÍ'€0ih�öB¾ž ¯ôÐ'~ÓHï¶éCM׋>øCàC``݆+É—æW{�&{Íçf±¦rP@ÁFeÿhGj³vþ—ÔÖ~ábz:0~Ú‡ ÜWà}"~«–}çfXv*H‡ì4÷fÙ÷ªGz"ø‚FƒÄç{ÖoGPƒœÖ|xç8o×hW„üG>¸xô'€8¾¶¾6°m΂Cƒø=�øVuèæsBHvØ7hY(JF`8‚$ˆrBp‚À~ôxsÆmà€D ƒ�—„’…6(‚ƒHdp†N6†‹hvWp}‡˜þ'm¨„Kðbß&;èFè�ˆ…ˆg…‚(qHƒööa.yàGjd!¸l1v}¤7‰5hÿŠ(l6F�À÷‹v8 %HzH|)؇Äè X�sf‰Îhˆ¢ˆˆPà_G�&ŒC@ˆB„(�t((7ŠO�‰›¸„«a@—˜à¸j¤öŒÈ˜Š˜Ç‚?ŠHz|È„g§WL‚x¨bºXr¤Æ�uXŒµpŒC�Œß‡…}HxE@€À'«ñx…¨ˆaÀ{Icpv¶G|Ù)�ì’¢X�R�‰I¸„¹x;Ø‘G ‚Á–{R,y�Iˆ[ö…)h�ÿ˜–×mЧ�+iƒÙ|46‡¤§Ž Ù ™‡(†}XoGI©g3ø}☒H€îÈeÿ›(d\è€?çŽ4÷h=@f‹G–?w}w7ˆR'qgib)×|éˆþµiß—ŠfI‡I·jt©nIq"WhBY«˜�±&_Ø�BPŠKp…^çzwÙ�yy�k©XŒE€’U ª ª©ša†“¬‰dv#ÆbL¹…˜‰yé&`hx«ÙšYš¾ÇtЋPPœ–G‰¼ù“@&¼yÕ»Yœ©øm^iøFŽEÈg^iœ ¬ÉmÒÉš…‡“­ ¬©š�×hÎIf>¹�Äé�X8�½Ùm„†~S(z¿ÙšÓfΙšðsÀ|ÇšZ6bDövâ<\hÅI`ä©•ÿ^IœÚyœé¶oÿùžžžPiZÇšGyšV¶ 3·ÖøÆi‰¢&*L”ö•aÔHi&ª)º¢h `¢iy£6J„Nð˜6ÈcY r'*‡;š¤nØ¢"š L9 G¤;J¥Uº£Ô86ù¤‰¥Â@‰”¦b¦!:`\ŠB?'©·�eÁ–š€£i =7w§´ña7g¤uÚ§~ú§€¨‚:¨„Z¨†z¨ˆš¨Šº¨ŒÚ¨Žú¨�©’:©”Z©–z©˜š©šº©œÚ©žú© ª¢:ª¤Zª¦zª¨šªªºª¬Úª®úª°«²:«´Z«¶z«¸š«ºº«ÿ¼Ú«¾ú«À¬Âú^5ÅJ =À§Ã:O$ ¬^Ð)@.9P$�OP5°¬8Õ°á.@€F@$ P�)��Ïê9PÅ5 ­Þ =5 >-0$P)P-�ó Í*ЮW°°ûú<%€®% ¯Q`òºGЯRа�À$€±ªcB0²P®>`€‘­*ЭaâÓšèj9²Oâ:[³DP-@)P­U�K*`±ä°2Kk8 ³.€´N;­%P­Ë.е-àÅÓKÛ_ëØZ­ÿ`µC�µB ¶A[šK[µE ³`[M[®BK²åà°·°·€±P€;±B�µ> €Qt‹9àp·i+´õJ­N²GË8Pã*댫·÷ŠAK­9p³8KOë³G´8Ð8 µMë�«®P«³Gà¸QÛ¸ µº[ºÓj·€¡³ P8`wÛF[¬50°D ·L{³*@ºÀ³xK¸ÛµSÐÝj¸*ë(k³§;4û$ÔÊ*ð'*à¯XÀ½Rpå+¾I�°Fàî;$ÀºB�ü{-0»BP¯Ñ[E�½Ë*À´ÛZÜ*ÿ8`¯B¿ÙK KЪ¿Úš±ÙJ»ó[8�¿Cð¬Ïë:ëk›k/;&ëÀI0¾D »GFÀ²J�¹­«¯ k³P-z+µÿ’Â×¹\ÁâÐ¥Û»É[ºRÐÂâ‹¿… ¹9P½@·û[šPN\5𸷠µ[ü50­´öû8¼GìzC<$°À+|a\ÃÅs¾>à½ê{v<- ¹L{°Ñ;»)`±slÄÜÐÇPÅ`;,æj¾<‹­ÅŠÄÉû'É‹� ÀAûÅ:û¿L®4›¬¼;¸Û³"½�û>�‹ÀØë‚û/…¼²ª±Ñ;ÃEpÆÿqû¸9p8�¾æKÈ:ñÁ†Ü *€®{ {f€áÄmëÂqk¶ðÊw ¯YpRÛÆQP± ÊýKÍIPa|ÊHp³GÀkìœË«,³ü¯¼ÎÂÅ,-üÎKpÌHàÄs ÌÍ´+ÁD¸lÚl¾íÜslËE0ËáKæLÉ|=l'<Á©\ ÂŒ=ÐÖRϸÜÖ|ÏÞ°Ã-! ÁÝ 1æ*À'½s¬¼qKÌ7\¾8 ×+=0Ç0|f³ÁHË€Kp&4]½½±¹<È�»±CMÎ銽ÓZ¾î:x É¡‹ÒÓ°½ ½ÿõÊ·€¡~;ÃZ ´ ,­«¿�ÎH½·å ²�)àÓbËÒwM°}ûÑNà׫½·úZ~»ØY¬Ê Œ×@Ô\¯e¬Å$pÒC€Ö+¿Õ2·Ôš¬å«ØŒÝب[@›È8 ÚÚzØîë×0»·_ ÖÐpÁ±]° ÄT€¹8PÛl̸u Û…¾ÍŒË§>;°ûº²úŠÛù€ÍË]\ÒJÓÑÍ[TûÆÞ\ݺUº9ÀÛÚýÝàÞâ=Þä]Þæ}Þè�Þê½ÞìMLÚÞ)ar ßI@ßgà_ï 5 ßÕ°bsàß?ŠSp\8Au\xvøm;Z ÿ8I§ü­ RÜ8sh*?—‘%Y€%ö“� `n î˜.™›Y€$º >âÔàmd6i&žm÷‰¸¢dÖi~~('g†GgiY†ÅÖo;i`Hçj€™ãÅSkè·kªÆe>¨l°¶t¶f-]Fhai!Jà2Þßê¶lv÷™(z¥;ºD ¤Ñ7’Œ qq7æ�)¤Wn j‹pæ� ä®âWt·˜E×`Hç}XbgÊuf×�êÖopJ\–n6×nïæ|&âaÞ þu}+:zsDJêu—¥œ÷•ª˜Œbf}½7mv‹úµoGYˆêÿDàgÉéz¶ƒ4yÔדöØ„§~:¡šˆŒÜö'.~“ÍÞéÒ �Íh1þ~¼GíS¦”9lj�‰ç’/�ì¤|ù)|è‡B0ƒù“=àêO0y"8“hçŽÝ¨.íÆpâ8)ê·øï?.‡¸Š I•õäµî’%']ÈkѨÞØm"h…îŽð[èœ÷lä¾�ñˆy0ðú. Ühš×˜(Ÿò*ŸßrØŒR™€S¹í �¶®b@àdé켔€G�™'ð§éo™EÀìù>òQ‚Õ^Øh^zâ&oç(‡çDp•g&õ¡wî݆¦H�ÿ9ìêˆëD ï<¨•3ç~LÏ ßþõ“Î÷öGðtGó3äHÀêÏW‘/îŒxX’ìvyèFöI ä<ˆc)‚�(|;ßöÄ@õOO@æn¤7šr¸ƒU_à‰ïzÿ~àît.œLˆ�õ·FjNmõIŠ]Oqæ˜�ªÇnp‡ŒÞ‡ô'à�_ À¡ö¡«oȉ¡Ë™õ¾/ÀvÀš�Æ¡pàjc©¹šè©š›ænÀt½_Õ¢uL ÁלÏ9s÷ifùÙûÎoý¯çœÛ/ Ì›J|Šv€¦Å·û!‘a0z=��X4‘I%ñµ.uNlé‹I{ÿUã!£UF9Yï˜h(�ÑiõšÝv¿áqùœ^·ßñyýžß÷ÿ ù8 “b†!#%')+-/135791*3;IKMOQSUWY[]_µ8faqsuwy{}�ƒSu^Šo…‘“•—™›�Ÿ¡£¥§©«­¯±³µ·¹»½¿ÁÃÅÇÉËÍÏÑÓÕ×ÙÛÝßáãåçéëíïñóõ÷ùûýÿ8�`Aƒ&T¸�á3TF”8‘b°h¹˜ñÒÍ Ì"1ІIv€˜P¥‡‡ :ˆÙÓáe‡|z°è0cÉ�c�…c¨*D�Zò¸§è§N l€ÿ#Æ‹C‚t IDÆÈD2DÄÞ‘ÂGÎ7ñì S‚Œ% Õ»—]Œ¤KbÀô�Oà T¯Ì’T` gŽìÈA$ÁŒ|ø ‡…Ì›ñôà飃]%1ü仚u7¿^^?+pÒˆ ‘fBM[I‚´E"Œ&²£rÂ}ì@ЧtŒ­¡GÏö:FÈÒö±!ä�­j^˜eà» ç!r8€¡K‘çÏ ‰Q‡Ô5W�XEž@ïIt� t0*b@¯ˆ ÎãíÆ–xƒˆ*‰ Ö"Àëzú+‰â’Xî àºCD#HäƒÓ”à€éÿ`ŒñÃÈà ÀÁ8€±øŽ 0šbì#BøÏ  ="Þ;ï#%6�’ª ¸R �Ȳ"r4IÂ’$‚0¸ñ$ÙDüdÊ;‚Ç“%¯#Î ò6¡ü 6µð % � 9:v`ôGùh® 4eÌTÓ`à˜5�@�#"Ä”#�øDŒA…,"€%è,"+6r[0È03BHÒáÖ"^ ×"À,‚ %ŠõBÕ|è‘WY Ï•Èa¥#:`k‘°To•ÀÐMÉ-וÝ�°­ˆØdEUŽWSõtÍV]ý�ˆõ¤µ¿‹ÿjùîÅ#d1ö¶#BzÐAŽM"Y-FJbìÕS«Díå[ë˜áZ#<€¡RŒÖÜ‘I.e‘$ \×Ã]ÛÓ)ÛàE"ž½ j1`fǸN 8øTøˆÆÀKÞnL"N‰…�€ª7“n–^€SKT‰H»y®'õÚp“0ÀÃ’ÑN›’SIJ;]ÝMc׫ÊÀË#Ž\V‹˜¿xAÌFM£ØSwd³høŠ@‰ Dö¡Vcï4j¦8Œ˜#«àê¨'®Ä#zøœ &»#t$RœIlÉÏVÛõ×AíÂQÙU2n4Ô=‚ï—=róݵ`Ú16±ÿ€OuàÚ–ö¡xÈ‘~»Æ�oUó1²’ÞE?,‘ˆu•bs/Þ:üu¥àY�lö÷á/ €å‹ˆÕŠ•oÏOg‡¿“ýˆ™á»%$o;�+ýÖå4<l\ªÂ®¶â¢Æ%«ˆ±¤§@@�d‘À=$x4D¸Li½Ál) 0ƒ„.e-dCÒ?îp~À”t@€qÕnƒÉëAT¨šÜá»ã’Xæ¥f hùùÕ Â2í`GäMj(‘¹¨Mê©\ó&¤²0*LzŒ‹šªx¨ñ«uÛ[qF³ƒã8 uD‡³Ç#de~s°ÿ!çÔÇCE.R €§ €‹|PrHŒã,¹EàxHÐÁË8pòOPá%‘¸Â"€•œdå¹C€ Q(Úc–¥SÞÉ#cZ’°Ì€”‡H|õ "€„† e+_‰)2I2”¶eܨhÉWºo��ËLÎp 9Hæ"6BVM æœ@:“à¢A/g8%…En2’ŸýÔQ´§†0Æ$»Ã‹9zárXó˜�Ð7\Á¡l0fð(#ô’½ .˜Ô,ÔŸ%5i>`@N &‘p0eKé€üí/'µéMû,˜*¥¢oÀ>ÿñ�•-êÁ;ÅéQ‘*ŽÀТfÖ i“U©R¤*/0êT±šU­n•«]õêWÁV±Ž•¬e5ëYÑšVµ®•­muë[áW¹Î•®uµë]ñšW½î•¯}]HžêWÁ–/G"ìa £Z&–±�Ê'YÉ2$+ìœìe1»!f–³�ÕÇ›<ZÑÆc)£5íiÏá‘«¢–µ­}†Ý\[ÙZcN³µím›±YÜî–·½8âP{\áž‚ŠÃ5îqM!%ä.—¹J™is¡Ý@´IºÕµîu±›]ín—»ÝõîwÁ^ñŽ—¼å5ïyÑ›^õ®—½íuï{á_ùΗ¾õµï}ñ›ÿ_ýî—¿ýõï`˜À6ð�œ`/˜Á vðƒ!a O˜Â¶ð…1œa o˜Ãöð‡Ab�˜Ä%6ñ‰Qœb¯˜Å-vñ‹acϘÆ5¶ñ�qœcï˜Ç=ž/xàƒ'DžDŒ,‰ÒùøÄÈ‚“ûà ,%  \°„+— M R@‰T Éih TÀä÷àmi³zpe ÌY¸ .�f$¤ ÌJèAx ‚,+�<ó*Pƒ%è9R^B –œ¸ Ï¼`´šG[ƒyÓkèA ZÐQ#ùÓƒ�—‰@‚H'!%˜Ãª�€ÿ ÉZ.3\`j]TÓ§M|PX“AØ{v¦¬tz¹®D¼Pl^8»×£…¶®Í†dÁÕFX6µY¢/üú “Îõ±��ë ¤ ×-pºÑ�ëw!ßöv»SïT Ý¹þÍ”spæ › “æs ’Üy‡š8h·¢½­0ÛþV8˜=vïu»;×Çu À\QW›¯.¨ÀÉQ~r‚/�ÍE`6èÚýæ*ÔàÒJ€²êí4'!×ü–· V¾„\ÀI¸t-ïlÓÛVþ¹®©t�üš5¨@З sH×;ÑE˜tÜ]‡ƒÝ8 : k°ît'šÿ@6{ÒMþtX÷À-ÈAjP‚ Pœäx ;¸Õ€ƒðàÏ2Wͽ �/aéoPAÆ‘ÐqÃ!³¦R–�º/!‹OºŽù—“!£ïAÄÒö#ÀàÒ5 AZ.ïÓw›90¼›£MqÔ÷�¯¯÷�P-t ØÙ5GBص@‚[Aðs¾p`ü5ðÀòV/ÝÍ��ƒJ#ûJ@>æ¡î…l¿ä½ZîžÛñE¨üxpûæk�èðç}^ÿ<æäÀûgƒò‰ < TÍ ¶-ë¯ Ï¾Ï ¨�,€mþp-!Ð ÐJ€ïè�ãžËèÍòˆÿ@Úˆ JàérM¶ð„¬æXðó,ÀØ�ïo®ÎÌÎJà"�üÈ Û°Î°ZDp H°Ü†ð2ð âÌ\@в¬5�àxM Ђý|] -v¯�é´è¢ð —OóŽ,à k0®ÈÍü¦ï«à•Í� Ý ó”`Ùà§Œæ–P ðü´€ïÎÖOË&#ïð ñ0 ‰€ÇMúÚ0®’MÇ@ÓРýª@hPýæÀ MÙ ÎÍ*¯ 5oò’•@ÅŽöÐ` ÉÔ‚�ïØÍ ÒÏ/þ´@ÉÐ+±­Ð× óÐ@«À÷ªÿ úæÀ ­ñjàY®¶Í©ŒƒM×&í/Ý„ñðhÑý�‘ùÚNúv`ïЯ.�kïy !�»/í®pÑÊÙ^p L.åx�. åRnÉx@ QûTÀ O.‘ SÎNS J 4 øî =ìŽ`"êÆ ÌÔå:�ÿNî•­D2°-¬1bøoÝr°þ¨"+@æ<ò'²"I T@,NÐ*²ˆ2#ñѬ¯�pÈ&æÑr~Ð œÐà ê r rÒ�2)ͪt®R,Íò,Ñ2-Õr-Ù²-Ýò-á2.år.é².íÿò.ñ2/õr/ù²/ýrã/ýr#î`B Àœd£îFODB¥ÈD$žÊ'¾@$€k \$@[ ¡+ðiua: uá 35ê˜f�^D07)ê zD 8ÀFg1Ñï`ó4ª?¼À°NƒW¬ª×b8& „ áH‡f 3#á"@3Ãò(À² 95ªz3 �Ä55…ˆà€;jŽÞ¦ &' Ö¨mÞà\¨  ,ódô>€¦3šÓÀ†�–`f¼3SÊS4È ÀóizʨȠ>i=‘€=+á|a"€>`.&zXn±þF^#–È£ 0ÿ@<®>Ž¡JB6üƒÆ GŽâ¹ÖÓB'! à:F%�ldÀ0´'¨gCWÃ/|d ¨(°Šàp t�´ ²(E×€8¼Çz žR3Ddfà>í�Ó$À3ø3BhÈG[CT6ªGÉ`@“`žTÆ ÎS1ÿIJÓ R²t$ œ<óz`F;&tÔJˇH(Ÿœt pM¡ƒˆX¦ Ü à”šTrz hêtz .(aXÀSa&  •4Nµdà+T¢r@Q1uo@ÈQ…"@7ÈEÇ`RuEå´§rdSã S5³v ªT †¨ÿDt>s>À.d@YU ~ŠVYRqÕ tÕ*õ~zÕ 4õ(ìÔ ôhX-ÁAªô�¡…vVfÕZ+[Ý`[]6Ø4Vµ ´c\«@�úTM¤€À”IÓ§& J)EO°@ˆ#bæ”U^÷R ˆ;6Ç^‰ “Äõ[ÿ —:ö_I¨Z©Ž•TÈ1×€< OQÇkfhµ¶Æl rôÐtTÈ_µ`< `‹€,– b_À›(6(lŒ’ h [5ÖºõRUÔ X´_µ`PÀ\©Ju Rè_Þ Ä) V`a‰ ¤•JT(^ÕkµE†µÂôITÿè@½@œŽ5û$ U ´ €g�ö ¢„“F“8)‡©G!•^évvå•\‰•HÂE ·q�à™´‰•*3KBv < œ4ŠÆ ì)Y¥�‚5œ6æBU:`Pçv;8ÉK±t«€\Ò¶ _Õœ ”X T% 84¡”+pË% V!=™ÔNg oÑ +ºõ�6d K•€IBª ¢—\e b6‰H« |—`Ì×c/¤z•®¸¤_ dì¦2 A¦Ö¦™*¡@|S†è·}çJ2„fœ×̆²‡T‹Ö„fØI Ê�+~ׯÈ‚-ø‚18ƒ5xƒ9¸ƒ=ÿøƒA8„Ex„I¸„Mø„Q8…Ux…Y¸…]ø…a8†ex†i¸†mø†q8‡ux‡y¸‡}ø‡�8ˆ…xˆ‰¸ˆ�øˆ‘8‰•x‰™¸‰�ø‰¡8Š¥xŠ©¸Š­øŠ±8‹µx‹¹˜÷ rȺÇž¯ûô¡¸Œ!Ø®ZnÙ@ʲíºQ¦ÒÎ+SòÒ˜¯4F lo kÏÎÐØ ð0±˜2�óŠÜ � جÌN�#á�íàý¨P‘ýêÚ(Y“@q@àOá6­é²ŒÎìØL�›Ì"MáİöÎÍý–­î~Î^>Ì™ùVùérÖ¢ÍìEá@áÞ€ÒBnäˆÿ •e¢r�È®*ù, \O%1™/úQ û/ Zm â¬óªîÊÜcß°ŒÿtN*w�ëd­€LÊLMábOìtÎÔêíÊx9ûÐÌírp ˜9Ï~nǯô­ð0ž÷OÊ^+¥îî\Oa¹ }¤¬t\�6PàìŽú¸šYCsy ñV�$Q± ÏL ž/™‘JÈÌÈ ]±!ɘµ ÕT ›ÞJt $ð Ó—nç`âÜQ£[Ã÷R: 6Ù 9 1ÕŒlÛ@ï7îØ=ððª½,‘71°ÌHpüZQ’“à£ÿNü¤OòŠ€·Ía‘=n›¿ ±Ò. »©¹ªP&h{:ðbË8°}Àßñ©}`àØš5nðÎt°"Û‰;ò%ó®òZùø®90¥Y°¨ýZ T0 º ÖÚýd÷; ‹® z ¡Ñš:=Üà°°3(Dñ¥y“70 –Û¶'›ÑD1ÃOÖ<Úå n¶} ð~›ç Ž·:rúͰüÐmÛØ¸¸÷â½Tõ²�Ùlqõh�²³Ûã˜ñônÏöÄ¢·o²Ì�Û\�6³�Ç€‘¹1�´eBæŠZ½ƒ¢A{ Æ Þ\àŒ,’ÿp•1 ³�æT€¬¡™)-Ý\@°Ï”¿ðº+¼ VZ–�€Œ!±Ë1ë±û R&†n¥ºÒ°[Â#‚£Ãvø@é�vP Û'!îä‚lÌ’-ìÆ£9Ðä® é¬<ûrÒ]þÔ�Ït°ŽÕ€Ë—œ÷íäŽíš“Ž%MðÎÄzϤ ÍRà( °,òÄMÎÕúñÍLNÏ~²£�Ü –’ Ñ êý+=ùÝ뺒åž2èœrÒuû)#Ý ¾òî(,½h÷Ѓ‚,Ia©IÝÃŒ1ÕI,Ît�¢7�Õ1lÿh]ÖmýÖq=×u}×y½×}ý×�=Ø…}؉=ŬÿbT6ó\.€‹=SD8 Ñ@7‹b*ÜÈØjêÙ3åyÞ >è@j�Ü;¡¸˜ôL½]:ÀÝ Ä½‡Wl5% žcÝ£#æ1ác¨z ºcZ8€H haB*„~ç�:4$ŠŠ@çÃ'òDß“Ä@¦Jz@<º Ä£2Q–g\dÔñÝ ôý§ˆf‹NEgb€K–fà�”hö£Gƒq€Ê[Û€6�À ¤À �HdHÙ Kl¤ º$L FIéé8 G~ÈElÉw'vä[£ÝeZPØVxÆÜ#D n¤ÀÞn×¥Ip… ìgƒ“ˆŽÈê½hGÿC¨5Ù›¥æ©(ö7UóŠ|àÝÑ aÁwËØ_òãæ�oôæV¨@ª ŽþîWÃê/·jö§H·^M(¤BÞ„^¼ž7#ô©B èu ‚¨¸@5ÀónâfA‰@<“`êË&Þ%?!(¿®ÇkéÇï¦rbàrÒ>Th¿2 ø�ÿø3 ç _·ý&J <_ \;¿{™Fðk?"n¿ †*Yxo¨çö?¿0–d3O’6úm§ñ©uë+$ûùbûŸälPèžO©©ø ¾áP "}/'9Ìœ 26HrŽDA¯xn ÎaA86ç5»í~Ããÿò9½n¿ãóú=¿ïÿ "ˆé`è @q4!½$e`úl˜U ¼œY%²�"él:õÄ8e”½Ì"éah qµ~2:qt�%S¢>CGKOSW[_cgkosK D%a ˆ“ޏ;û”ÉÇgðð{V–­!íü©I"Àߘ0Ò©óDdƒ»xtø‹ƒ`Œ mðÅÉ*cè¦r%Ë–._ÂŒ)s&Í>16p˜çäæ®3:pöLƒ‡e5ÛôÈ™Šè­1:ˆú¡8&†¬£V¯bͪu+×®^¿‚Õ& !ÿÇ¢M«v-Û¶nßÂuÃdY�qïâÍ«w/ß¾~ÿ,x0á†#N¬x1ãÆŽCŽ,y2åÊ–/cάy3çΞ?ƒ-z4éÒ¦O£N­z5ëÖ®_ÃŽ-{6íÚ¶oãέ{7ïÞ¾.|8ñâÆ�#O®|9óæÎŸC�.}:õêÖ¯cÏ®};÷îÞ¿ƒ/~<ùòæÏ£O¯~=ûöîßÃ�/>ýúöïãϯ?ÿþþÿ €X �"˜ ‚ 2Ø ƒB¡„RX¡…b˜¡†rØ¡‡‚¢ˆ#’X¢‰'¢˜¢Š+²Ø¢‹/£Œ3ÒX£�7☣Ž;òØ£�?«¤�CY¤‘G"™¤’K2Ù¤“OB¥”SRY¥•Wb™¥–[rÙ¥—_‚¦˜c’Y¦™g¢™¦šk²Ù¦›o§œsÒY§�wâ™§ž{òÙ§Ÿ¨ ƒZ¨¡‡"š¨¢‹2Ú¨£�B©¤“RZ©¥—b𩦛rÚ©§Ÿ‚ª¨£’Zª©§¢šªª«²Úª«¯Â«¬³ÒZ«­·âš«®»òÚ«¯¿¬°Ã[¬±Çb!ù,ÿ,±ÿ€>‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç� CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳç&-<´hâÁ^ÐFZ„ŽJªtiQŸP£$¢@ASKTQ¸C¡€# ¢�z J©¬RÓªÝw¢ª™ÿжkËàkXR!„(Å@�Öµ€Ç+¢ ¯WLrÙq­»¬ØQyÍ’J,¸²år6ªÞ¨ú«_wtí>Ev/)(¬\^ÍúÙ> ((‚©oçu¡ßEVrëßÀ÷ñ¨ªz³�x˜áàAä‚ÐÞ@¡Z‘7 ÝXþÀ9t(P<õq½¨–Ì�g_t#ÊPL|O´ý}ãi±‚ÚAˆJ-&Ú ”†ïA !6„‡Èt…7^y>œBwQ¬g]P0(Èt'㌣D1Û `™–d¹åÈIWUƒ„hQÕw<€åcÿUd—XV(PµdD b…’K:0!<.Éd~…˜ å’PœWØ€‚Ð¥E�>q[!²y馑bE_^úv g†ð@Õc¶ å˜nÁ†H”^*p‚6:@ã£�vBÕ¢‚peU"V¸å€ V '%¢9–ˆ 8j¥”›¢€ž[™)@a_=>„ÝhH[U=p‚ªlRJH¦¹F�‚{nÅ8H�D´ˆ‚ JA*cŠˆZiW}!¬ |Úg­Ö†…”¨éUˆ�]Ý7l³*ú@*`‚E[tŸ Ò—’ºBÈ-‘qñ€ öVE£‘,ð$m&èn†G„±]ºbW‰ÈÕêÂ…¬ÙÿqÏ–.xþbi`j¡äm­:æ JnœWY† J•Á‰HËqU£ "²fEÖêë äv<©e5haD\–©‚=^ ð¦¥z2škƒ.—:ðÕX+BXÔ6ÂLaET›Ã¿9­ƒ-vœ®‚çÆII U†BVÑnÛzXÐÎ ×!~Võn´[Zw‘ç–ƒ+®Í>'L•žÃ½õ"Ÿeç­ÜR_*Hå5s©iÖ¤—lȘb©±…ÔÚ©H"­¥Œ¢ž¯Zí`ê²±«_!Tg)˜Eb,a±·\•×{Žó!6º"+û–×à‡{•¥Ù¦£¼ñú+ÿ"Z$–WßÊë^úú¿f·úÇ*pxö©¹¹vU=å¾"«çú׊:Øß±¿Dì¯rœ ö!3K%‚++#ª¢#ª „¹ � Ó»= a† Œ¯,¸ª$�}( Nåîc‚º°G["á"RW¡Ç B6HëËÎg<L*f»A $èæÂ"‘,Œ� #ž…&H­D^æ×<É€ÎQ0bsT1<µ­‰aÚû#DÂ�0…h\MÏU¨Bô0„!YWšb? ½=£!êäƒ(B‚�l< ©xéf4†ä¡çd–ÙH¦U€\�õ½¼u†2…èÍž<—€¥ñÿ“«)_© @ÊR–²GùÁä!ôèƒ þìL™SDå(¤J)>Î8bCÝLÉK^¦ –ü&)ÌBTN,[�ÓK(„ —¬á ñA³ÙR2ÆI`üÎÊnJ¥`¹ô! ó¿ŠÑЉÀìËûRÎ_õ°–‚à£ýjGË=Â�‹� úšGÌ}(‚À!#¼¹ãLÂRã+„YIˆ¼˜¦Ž‹˜—7'º–­-¢kŠ+æ­Î)NÕh/£þÌèÅiÍx&Ó`¡¢"Ê?@Ç~;¤¦F ÑÀ™~ äyÞ‡(ˆ#Â'ÈOàRtN1Âsƒ¾éKÔŒÉ$Š:*§[„ý:cœvú ráÿ»¡üPjÃMZÕ8#e¨-u„+Ö âE(Â_*WÒœÞOw#žVm*;„忯� µ¹‚ÉU%YÞE®T†ï¨}¥AØÔ§:V'TÄ`ÊÑ«bÉlÆÜzdMÔj¤ˆ=i+»R»Àõmeœ»U¾g“òGMLdŸp:³¥Ô-zª–\-uî¥m«– ."ø/�ýtt�MnMfÙˆV͇^ùA”jɦ3j¡Vˆ® ó˜ÕB÷ªRŠÂUpE„-ÕªL¶!�5'ÛÅŠ®=íçŽh;¶ÙÌç†áâ?»²3ĉrU¹ qFxØîRVO`½ÍÍ’\å:&ú|ÿag1Á9`º‚¥5WÒ›YÃ¥Òp,‘X.y±(‚”7wÅŸu@•G„æ|VUðDM™·ßõŒï©UyK<7·Ƹ-ÄÑ ¡4x]ÒÀ×,„ ¤Tc$I6•|°–SrÔô�éJK"Âcài˜E à³ËoG…KbÑâ^\^4U‡æÂ´µDßZR�£ÄC‚+„<+}_ĦBÅB†È³�Š`¬&‡Pw …²I³¬EzƒVݲ¦}²ŸðØ œ”àA§\* QSç¯}ºvÑ¡SÇBÕV5!¦s+ ÚVÖ Zmë¼ÍyÓÀ6MŒCÏ>IØÈNöLÀÿâ;C\WoÊŽ¶´QÂÃìðNÇž¶¶·-’À1é=T6+·ÇMîŠhALyºu¹×ÍzWân·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;üá�¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈGNò’›üä(O¹ÊWÎò–»üå0�¹ÌgNóšÛüæ8ϹÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéP�ºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp�»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÿÂþðˆO¼âÏøÆ;þñ��¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°�=�pP”+¨ûI©E`rÙFB¹€¬¥º£”@ )`„T@ T`øœÀ�Jpûu¸`úˆ¨|-\ D߯D’�„ HA U0Drà‚0¢. … ’   $ú„¨þ©¡…+hà9PÕw éÇ}qé( Þ‚pà~‡à(Kµ«à}“@䀀ˆ0€—Pè)°}%°{“P()°€…�(Ø8�|‘�8Upÿ­`ƒ#Ø"è?8 A8Wƒ…pX¨ƒ;RR U�Z`„¨ÈG…�@�X%Wà»ç) ‚ûWW�WèX9PƃdØ~RàJ˜†ê†ÈÇ„¡æ„Ë'…ăl˜xh. %à†jH�Ûçk˜!‡úçR�k(‡X¨ˆ.>€V¨Uâ‡fhˆD舨„ˆpVЇ*°~=ˆóW°‹IP‰Š C(Uá€Ẋ×g{<0°‚80Œ´wI@ƒ’ ‡.ð|‡pè‚<`ƒR0 <°|8�0�8p‚ù§‚ë׌ùÑÿ…�Zè�ø€%P´çÃH ×G{Á˜ïh;wÈWð„†PŒQÈ~I°‚–‡ZPˆk8„90Œë7~¶X8@��Ž‚`ƒ)Ђ9À Ylè…ì'€„@Žs¸„†�^ˆ�å¸�ÿ˜Áx·ØŠô@Št8 )pW k9 Œ‰�ƒˆ`ŽÆÄ‘X�…p‰H x�ùáŒ�à}F¨å·¤ø“cȃ’ i‹1¨”…‡„Ð�Wa‹ÄSˆP‰Òw8�>iÀ‡UPFXA©–éH˜ùh@YHù9CÉ’õp~�8† “ɈjO©‘x3xÊȹØÿ‘½8 ‰™}†°’ʇ…*ðŠ é¨�‘`Š’™/HR€…R ~DR˜ƒP6Y$ÂgIà‚„@™‚0š/ø–‡ ™Ó˜}¢›UX$<9´È—ì0…¸~lœ� ÍH‹‡¹²Ù𙩂ç8Š«™…q‘gi3{Ù59�°Y†RPŽó×  ™9`…°Ž%‘Œ0—tY�à7€øŒ°©˜9�×)¶‘78™vÙ à)žî)åi9�¨«ÙŒâ‰œÂ¹lxIPä‡|šP°™šŒPšE²�‡0ŒJ�ªÞè� ù˜Žð’„P‚¶SŽ8�)ˆ×i;Æg{SXÿ– ¨�Œ0Ÿ!yƒ:¨ée˜͉—U¢¢\’�‚°—ÞW‚0:2ª ¢•È~8¡Z*°}¨ ( ¤×y�Œ ‡È›ÿé :[Š‘3š,Z1¿y—„À¡‚P¢9—²©9 ¤Š¹Ÿ>ð¦@ˆÐ ¤˜ˆ?š‰iú§~ê È�ý9‹jØŸuŠœi¹�왥ìÀ�yº<@™FIŸŒ°Š’élã¡“œ |:ƒ:›9І_ú§AH§>@•þ©z} H™(j0`“úi …*š¨¬*©ƒ�¨³j›/)«¦›‹ð¬‰°¦Y9¢˜Zbº§”�ýÉ0£8¹ÿ«J…XóƒY }ER¯úži¨µš”li�f8Ò·•RÀ�“™8ˆyÉ% ÚåJçj;ˆVÈ�¿9�íº©eÙ)­¶£¬ãj§÷j¯ùºŸ¿i‰\¢®¶Ã®„�ž™ª×ªiˆ¤�€Ž)�Œf(§ª‹2;¯Ÿ3³±x±.[”�¸•ô8‘P���6 ‹X±øÍX‡º·|ṑÐO0W,LJ ý«/AÆÅ=¼Û» APÃ�P9¬Ã/ì~ŒKÀÇŒìȘPd| ïK TÀ�ð“L �ÌK�Ä‘¼É¥PÉj˜‚0—Ð2àÉ[ÜŸŒ¼‹Œ ƒ, †L ßëüÀ ¬Ë�P˧pÈŽpÉ�€¹ÌÉ»\¯Ì ’Œ À,̃€Ê–ÐTÀZ É®œÌ±Çï[ ÿ¢<û›€æ΄@ÄPA°ÄÜKX€ïkмÿké;Ë‘ É1€ë|Ǭ°Èö ¿QŒ±¼ÀåËЂðÊKð¾ß| ÍLÎ÷ìЄ ÐëüÍè,ApÌ‘@ÌÖÌŒðKðÎÊ,¾/°Ð �ÐZÑ0Ñ–Pш Ñ-]Óÿ[KΉðÑ‹0n¬Ê�‰¼Ë<‚ =À/€ÒâìÔƒPÎò¬Í ÍÀK-@ÈZŒ,¬ÅI¬àË- ELÈŽ Ó@ÀÀë[’Àn½nÍpíÖû÷d-É&}ƇpÐ)ýѼ¾€Ò>°ÿ,làÿƾìlüûÜ ‚íó[J�Ø?Ùƒ@¾•@HÀF0Fð‰@ÈPýÉ~ÝÂà+Ø„ ¿,‰=Ó ÖõÛÂ}¿aíØ²­×† ÙŠÔÓÜH0C ÂG ˜ÍË#hÆWá Õ)ÍÈl]$Y=/0Û„Ó„àÍ…€ØfÝÄ„`Þ�àØê-ÖŽ-Ö‘Ì|ÑÓÍÈ?p݃ö=ØØ<É�@Ó…Þ„° -àÚÍ܇€ÐÑ� G@B‘HЈ Û¦�Ú‡ Åùíø=à þÝãýßmà]àfýn Îà§| N@Å‚ °ÿÊÎ�Ð=xÍ9¼Û…À׌œÌ—½ÀL‡|ÍJa×Îö!¾î7½ >Ž­l>>KðD"ŒIlÏ´}Hn:­ÝPmU‚â<Ž CP‰KŒPÎÍ}åYNÀG-^^ M.aàw¼ç„`æ`inĽ Yˆðþ]Ý=¸Ý@ÀÎ�KUîÜ=^Ô‰ èæœé Å\çƒ0æ‘@äá½ä‰¦~ê¨>è€ ÒR>ß¾á‚@ÃVÞå¥�çèl·p烽è5�âšð l,߆ ÏƒÝå°.⇀ëÂ^ëÉ®¾¾ŽÒÌ G0ÂÁ­Í³Î—XPÿΰßTNݾËÜ̘®é™^ÐÈNë~Ö9ü䎰ò>ïô¾Óémé%ò®NéÆ�Lº> yÎá!~ËMê�®ê›�ÉÂîæ•@ØÍîïÊþåÇ^ðEÝî^,è?í BmŠîÉÚÞzDòAî ¿×â>8¼ }iMË¢ ïNÌêSÞãÔMà‡@Óž¾Ë^ÈèðÛþÐ!_+Î Mì…`áÕ:ŸÄ-oë¾ËÎí� àÐÍn|F`ʆ`ãžÌ뮇ÏÁ-Ì“ÎècæVÞó÷[òŽðˆÐä2ïÌøÞê(ÏÈ$M?Ü;í…`ÏW/oÆ‘­Z.ÎÜ ÿÏÐ,܈ L@ã…�D2\_¤Í~Ÿáwoøz?ñ¹NÏLŽÎ‚_1…/¡OôÐüñÃ-UOà“?§†0ÕžÎÓþã=˜Û®]óxŸòBÄàîÛÍ÷¤Ý�=€Þ>Àê� êîö¢åú^÷¯>ú¾oÇã^ån(¸/æ]ü»½¿IlÌ<¾?pã.îZ ìßú1>ޝJÀðãõx|ÚÝ?ØåMÀ? >ƒ>K„ƒˆ„/Œ’’‰/„XAŒ@1ˆ?‚–A@–ˆCZ©ƒNBB–LF9„YF2¦‘­„K�½ÁÂÃÄÅÆÇÈÉÊËÌÍÿÎÏÐÑÒÓÔÕ½ ˆ?â?㼃ã�㋈/Þ?@¥ˆéÛò•àó@ AÄ…ö‚܃´%ÁFBl”šÓBÒ*0Òï5ƒp€¢Îf£Ëq¢o4Æé³L†F1ŸjÄœN?¸LJ­c™ FéƒpV�–|cÞ2‹áÐÀIp§\ó¦-aɈ¶¡5¡Ð šK(/¨¼#jQ+p�Ð4Šel…M-Q´‚”e@MBå*VeðF}Þ(û¦@BÀÀ§Áhk‰z×Ar�¡ãjUGÝõ/aËÿ�„H—‚±„¡%\€Tc–Cåêò¾Y¾ŒÆÔdù|é1pпB¯M kcÏÏ] ­¯£m:{1ÓA`ó­+»[S�ÑÙuŠÎ²ë„\=£&Ëa Cm’€nf“øQ^£ªË¸n}°ÖA T ‹ü«%4@Ychžˆ°n/äi Ôuw©`®ç¶ŒÞöÛµ™0.ÐX:wu©� (‡ \ˆ%1£Ã°ßð ˜Õw~í ´To3�QX¼Öp§Å+}S�àbt÷yð¬'!Ô©LI¸Ï�ÄXi*ð+ €2¢’©à&[�›×ú¡WšŒ(hãøarÖ°¨Ì�} ±˜JVV%ÿ·WýñO�êÅ®:œš&§÷·ˆ¸€d/JÀ×XÅËàoz5M3õȈ`zSpãaÔ]Æk>QwX&ô³Y#r‰1CÕ°v‘ÂLàäR{Û]nø¼ÂPo/QæI"Ûkqºm1I(…%šøjR `ш—„÷•L~Ûô1r�Êe:Ò*˜'b2MB \y ìtüä×è¢Q.•8°Ia&ëR¦à}i­dP&4y²°¶œu¬Àa§ •°«?è1¹ª& ³–ußø¾Ä{×Ä¥«DY7[ÞÚ…îS¥o݇ºT ÚUS6°³,ouçÜŽ†˜~õË ‘²6¼¨5r`ÿ0g±à�)dŸ‘œuxBpðïóø[àZ xÃD*¸ÈÀ7ßûÎÃA™ “üä(–SÎò–Öxã¦ËgNsD`1i&­¹ÎwÎóžûüç@ºÐ‡Nô¢ýèHOºÒ—Îô¦;ýéP�ºÔ×S�©ëK ñ @U$ñ­s¢Fк1ÂN�i@#âÁ¡ò°½2‘ßÚ>ˆ)ahB*‚Ð0´§Ýê·z�àÐ Ás#3ÂBAŒQ€-‡L/Ý1Ä›Ç3–¯�P¤qIt GÓ-,ÑùÚD~ò€—ák“y­ž”`yzƒ­_¾õ´oô}6�ÿ �øKn ºÙ§¾V¯¿îO”|a@íe2~*Z“ê[ƶ÷Ü™”² žÒ?þ¡8à�Æka €‚@€œ`û3Ñ‚"´ÿ0¢ù–°¾%È_ö%àfP"ƒ°Àw˜Ax‘· &{…·öÇ#ÜC€e·X /€¥€H `¡°Ûð€Ñ ²! X°�W�ðñ`R{4�}`‚Ç€ŠÜ Z@� (¨§Ðx:€ð€ð�…ê× A‘‚–à{ûW‹ñ}â(@1��Þ0 2Að@°á0Ðx/ÿÙЂƒ€’ ˆ€vÐA~˜ÀÀfh Ï10„HÇ@ i'gØ A˜�°|ø“§6±ä÷‡0n˜‡‰ð ŠÁ[K(ì÷Ø×ðRH�¿p Ô±úqA@ /°r BxKp†RïwX‹·Hgøà‰¾€ …¢È cy@Ä� ˆA W˜…ˆhy°‰Žá7Ø! rHyË×~¹q�’�‹Ñ¨ Æ`š!xÏñ8˜ q1ÐŽ>@ ÄA ‰…@‰–@‹Œ`m÷ˆa£b3Vhÿ «­h‘�`Áhv„�‹îB€Rókx&�Έ[�@  �“ЊK ‘ˆ€ ­àÀÂg ~ø�àx(?œA“,IŠPxÙçŠ-‚{dh ›ˆ‡•q‘„ &‘’°„ýèড•uèpÁA0•À ˜•_É +¸‘`Ø�à‡•Yùt˜”Ê@’P1•ˆp–œÈ{9é—„À•¸Ž` ’.¡˜©ÐzçˆM ~X8”}ò6aŒ´ñzX€xäW‡ì§ ™> ��{a‡„:ˆ©ˆ–h °›±éIøg‹!—ÐŒ6Á„“A`³×ÿ–"ñ–˜a’óh4X‡bBÉ ¨‰†@S�ûǘƒ`˜…€˜¢Ø AÙ ZðyÁÀ“Á�Ž”(±vÞ˜•+yK91†ˆ¢Išœhš�÷œ:xœÌ�KПþéŸVé�’ pˆ�¬ˆ@@ Œ ¬©”l¶éK€˜è0�™§'Ù Û€ŸaM‰�Ò·�©Pv½€Là|9€“Yžy‚qH‰xíÉqƒ­`—ù‡{$: ¿™ÚÈ j  °˜y>Ñ ,J2è–”÷汸„y¹œŒ$�?ªy°8y"Úv=Ê˧Gð“½Ð�½@ž.º'‰ÿΈ”ìÙê óù”©Ð’½�¥ç�dI¤¸©ª ò€4™@AZ˜[a‰ù NŠ@БÎ@„­ék:‚ɉƒÚua¥`i&"�ø¡y„y¨Ñ¥h�¾ ‡Z`S�cþ©ãÑ¢ij'[¨£À¦+É™ØáŒXsÊz©ð[ñ~tg9¡{Ô §*—Ž ½€�@¡£¹ŽZÐö’.’¨L{š é `ÐÇ‘~ÝðïPy)‡Ëª©Ù¬ßÐ ZŸ|˜@Ûp›©0¢¦ †8ÉpÜû7®g «±J'ØÀ«kè �åP Añb‘�ñÛðÿÔA !? á æàŽ�°?`€œãðìú;Q›å­E:¢ –�ª'±kø€ÿÐ ˜°BKƒ`|É ¡”å`¬ ù&2v©$‰[X�… ƒ0é¹F; (;¨ð"„ð1Y¶7A˜fÁ±ÆÑ{šÌš Ôš@vq·CpC@¤ù*™;(ϱËp~‚+ ÄБ#EêÍA{&¢§�ƒüØ HëªÈP¸Þ‰ˆ…—¸ÍиÌ0˜›šá´ÈJ{•Ú hú·zTHœ¢²‘‚ÊPzP¨ ˺Mc†ÿ@ºéÿ…‘2­µû•,i¶º{¼ÌЋȻ¼ÌÛ¼Îû¼Ð½Ò;½Ô[½Ö{½Ø›½Ú»½ÜÛ½Þû½à¾â;¾ä[¾æ{¾è›¾ê»¾ìÛ¾îû¾ð¿ò;¿ô[¿ö{¿ø›¿ú»¿üÛ¿þû¿À<À\À|ÀœÀ ¼À ÜÀüÀÁ<Á\Á|ÁœÁ¼ÁÜÁüÁ Â"<Âê›0#qî‚Â$ì+“„gP‚;üV(ÕK07r+Ü*1#bÈ03�ÖIÅ5 g"Ee~¢2©�X$e7L+Q#^Éà2v„0Ë– =¶ˆv(cLK|+w³gÅ0;ƒ <ÇÐfêñL”br[,ÿ+ DĽ€P8Ñ#c™µ;BãZQŒ ¬C9ÿ;Å#ÆŒp:”c o Zð4Æo/6hUY …_ÚãÿU=l?³@YÌ=U€2ÞcíÀY” Ÿƒ2) pµC9×C?\óQ›d¤N›ƒÈi¼)”møÆHlœSsÃI´SHÖJôAÛã>Œc0L´=ÄXB–?^ü\fõE›åB<à3I A6÷XXÄfÞ¤]äD`ÔM‹\Èîæ\g4ÍoÔQÆpÌF”Kb•Ɉ“E ¥ÇG@>õÊK% *ð_cUúƒ?°˲<4ƒ< ÏVq�`b† `†QiUb ¤eÜÓÿi*'>~ 3øæ2´1Cˆ sw´LÒõR´�Î>ÀdÜ $Iúœ^ÃÜYÊu…JÀ±gf П²o* Ú4lucp,ÓÕPÓ7õbeæ[ôƒHÎðYBí6­P¹æ6�Õ`>�Й%`î”Ó,e  ŠæCõb­På5>�ŒT:lÓ™âo$Ej9Ç V#6$ÄU™Ì'm ÐSioóÒxµ=[]KuU?ÜbÏfÊðäÂÓ ´UP¥ÔòÓJ•nf• 3ÄU_mf®Æ6R-FWƒBâÃÔ³a%íM!�Ö™W%@3Fì öj¢Un m Æc0÷̓ä\êõf‚•b¯ÆÈÐ3ÿJWÜ ¹m ³%Zˆ‘>™ó\hÇðT\ÕÏeeD"ÅSˆ¡4¿¶ÒSÖb@öM óq¤Ý)Þã_Êð×m£Ä�iLCX% Æÿõ7[dÒ´aThÖ UlfáØYÖ_­@ÞT%> _÷\Ò& rl *04}ÕÝž²7uí�å2pmÞ•eÝÅ€^Œ“Õ»Eœ]lœ[,%>6—E[úcÒ _%:ùŒc%áÿ-C¢“à�4à“Ýp¦ÓäÙ ž)FFã'.J3.:ò4LÿÓY)P^>ƒ èŒA84X9–Oó5 ÄIÇPaE5nÞIxeoòT7’5J}Ab33ª¡FªQÿV»äãWËEnO�¤ytGµÌÖ©€Y÷Óàï&á;.)�¦ ™ãn¨Si‘\Í3tÖ|o”A“µÃWsÚ%p2òóNïãkó–Yˆ2Z–nœŒQe5nçó>Ûè(“°l5!ãèÿõkÃju:tÅfZÞÑ3ã1§mã:Mètè 5lãö1•^T¹¤­öak$o&oW“ *´çœ¢_ëÍ '6´p©!dÔ¾JAì ÖÞ —Ó6dÐÌ“íÓ­A½`íÄÁq×® G”î�DîÁpp¾[ †Íì•"ro5Úôžïñ…ØúNï:“2Äïý¾ã ‡sX=ðŸð ¿ð ßðÿðÿñ?ñ_ññ°‹ñZòvÄàwáàu§û)ÔqO 娴Hú�¹Ëñ˜Qyp‡ U‡�Ûu¸é)YòÖ@óÅ ‰í¹ïš•;/ó呜Ã@‡æ±¸ž«Áô´·ñ•Qô¸²Ho˜J R¿NÏ)qõ+!V?ûšõìÑüwƒÊ·_ïKX ø„ €™ Š~h¹÷#Ÿ 8}ØÇ žëž°…†‰„¬óFØ ²ä—¤î0ø61釛Ÿ¦�yKx0OË1ùœÈ ®K­ÁƒeG÷à€„ÒÈ„sO† ña纩 úb)GƒÏ ¡PöQÿ³h�]—‹ÐÁ‹Ó7ö”ñö*(‡Æ0òÔ(xf€Ê [HxOõÈ ›1Œ’0 K xRZ¨îY†ÊÁü’€ûH»‚¿†©;Qdã_¯š`šè±©+ŸúB/K?K>‰>XA?>K@KŠX„/Š>1K/œ?Ÿ A Š¯//AX ‘’·±˜š§?1±/A„K@ÉÊ»±×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñê1°ŠKÙ1@òåå´lxê–¸%*2€�×±"¥‹!àš'PXÿtÓ… H®i‹bEÒ0‘ni¹ÈËÞµŒ cýÒÇʘ&8iR�’Š–ñ õÈP”ŽÞs1!(†± ü°–Nˆ@  #d×´°Ô¯¬Ù³hÓª]˶­Û·pÑíVÛܸQq¾]qU¾{ucQuîQ¬ŸÊº¨©ªØ88G€p¢%¸BÖ €q¢C×fÅËTkæXA€Ò³œêµ%@#ö•õWQ²Š‚dõñ‚ßµVŠ]{ÑÛ�Òx“+_μ¹óçУ»{Éð5êÊÿ™‹Á½»÷ÍÝ–”æPRùà±½iÁH Ö/@@7x¤€xÿÏR°g‘ç…–ß~¹°æCâi#Ù}S7Z|±`ðS+b`Ài1@˜yØ$Øáx§%¢(©Ý3a…úæCú–EÈI'ãŒ4Öhã�8Â%Ê0&‚]r#ŽƒÅDi$nßl"/ú8XìÇw/lУM…HUZ&Ä€E•ý�" 7j“_—_RâÙ§¥Yq×4çƒÚHˆ"…»Q„Í !.•�‡´å…ÚnvÞ‰ncZôßq96êè£�F*)�?®¤�ŠÙéå –÷LG-jã(?(VW‰°yMeýç—6ªÖâĔMF-‚&¦Ÿ)Ød,ô˜ªä‡Žw^�ÿ‰è(7 iœ±Œ5é´ÔVkíµØvSi`ÀòV#÷Éb€¦±Ôêªmä²Å)~•&¡]Ê^“hP®ëèÍÒÕâ¼Ù”‰M¼—Є�ª@„«Èé}&pÀ¬iqe"aÚ@Â_–ðeÆ Jân+…*‹·@T5Ù–lòÉ(§œÜ�X\ùão`kœ1LÜbƒì^/uf•Œ 1;{ol¾Ô›ŠVK§b4Mûg“¯°>-@Ô±03ó¿ÞºHI3/`PJXó†j"XàÒ>’È·5ÆÅŠÔçΛ�SÚLÕ2T$ü&۵ʀ.øà„_#M;‚¸Ñzn#ÙÄÛ€¸©ÿ—ÄܧáéªDÌ)…Œ³ŸäÑbôB1¨E{| â³ÕtêÕ ì?€ÉL÷üðâº?”õî¼ëÎØìÔ~{Î©Šœ ®sb¯0&Õî½ë Ê>ûpu<}e”öÁ ŒZï1÷>›ä8óßçD!~Ñ2_¯ìÛ+åôû�^”L»îÿÿ  à^v�™„C(Š�EÇ^ÛxÁ¹ªdÊÅ€„Ðè,làTD­¾Q‹9µ Bµ 0ƒÙÈ”`U*Úð†8Ìa£ö�…¦¥‡¬¢uX^�d¤j!³Q‰%:ñ‰PŒ¢<¢$€±Á…ÿU4 s†¨Ã‚cz)›/ñ¶1šñŒhLc1QÅL±pÄ(}Þˆ  1R£÷ÈÇ>ú‘p·¤ È@–ãK�¤ãÉÈF:ò‘�Œ¤$'IÉJZò’˜Ì¤&7ÉÉNzò“  ¥(GIÊRšò”¨L¥*WÉÊVºò•°Œ¥,gIËZÚò–¸Ì¥.wÉË^úò—À ¦0‡IÌbó˜ÈL¦2—ÉÌf:ó™ÐŒ¦4§IÍjZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºó�ðŒ§<çIÏzÚóžøÌ§>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡ÿ�¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0�©LÍrø ¦9ÊÁ gÊÓ“JÁ¦Ø)s*�ƒÿ¥ UÀÖQ“ÚÓ¦>Q °ITÅ¡)XU Ѐ˜zT@¨äP�ŒxP®V‹¬fuªZox±Â ­ßЂ ª -ð )êYìÚ�”`F)¸B¶ª صÖ†%À�JPTr06)pÁ¤\ Yi¬7ºìa7 À$$¢æðl,( Š+PÖ¦7M]¯ÑÖ¨ ±±Èe]P…ÓbÃ*(A ›�+”@ %�,l){Õζ°ãÐÿªP¤*…¤Í� ¤�Ê‘¸L­8LëÔâ b� –ŠÚkȶ¹•�wQ{ð>· VuÁID ŠåR7½¥µ­rU‹Y›à½×X® «…Ù*å¼Áeàx©[ÓþrÖ¤.¨€„'\�$À6®SÅcX8RpÁwSŠ\  8È ’€ßDà`¶”µp,ppä€W(î5x°U�â5 Úõe+c—C·6Æ� 蛼¦@µw�ð5\ �$§@ ~Ç�ïZ僂Ê0È�l+€\“ä¶»ßM‚RLKÔtùÉ_±˜¯Œƒ¿*‚Ç*Àê¬ÖFø®!þ®�ÁÿQUÂæà ÔmRÐX-TaÑCÑÂQkšƒG—7¹RŠ+}«÷¾CN¹ˆgƒæëVb¼»!rìoÿí-†Ó;ókT¡êâ8¿¼»÷)ÃÿUÞV Û߯ùägÿçà[× rŠ`b7ká^ÔÇS¢EyÞPä· R x>�Xk�Õb•Ç�>paÙwgìYŠ·yŸu`.åws<7c} !ˆy±¦‚é%€ßЂà@z½ÇZgx®�W(Ø€/µz 6|þ[— †W^UÅt%'ƒ(gU pwan9ÀrWð‚ÛPVq&rà'q  j&†Ü�„Ú�c—†We¶U_ÖmæÕ„ذk¦UÆ|o‡‡Mfrâ`kNgU�¥€. pGuˆV^ÚÆˆÞg„.%jH¶h²w‰ÆvF]P‚Ü0ÿ‚M–Ôµw5ÆZ ¹…x<—Ð\�*€Y™V\±Ø]VY&a…•iva®Ø\šöu˜W@v‰œ˜ŠWFŠ6WUˆy øjš˜y¸h�'VVuUµh˸Z&qŒ&„ÚàUÔÅw/ö‹''a1(� *ðŠËÈhNçaºyÖ&‰1eo¨9€;%�¼—b!thlˆk8 gçÐ�ùGZð…Ó'ýX�{V�qå�Û°gÙÊå�Y‘Û@�‰�ÕX½&’(™’£m*Ù’ b¿µ|Kè’4iXb¦S5™“:¹“<Ù“>ù“@”B9”DY”Fy”H™”J¹ÿ”LÙ”Nù”P•R9•þä%Ty•P y,ÓDn¡•’;ÎÁ=¼ç€O`Bp3Ð!Z„•=ð7ZÑDHq6l!—ti#b)$ !O0GÀFpL€Yà3mY.È—BÙÝÀ/|")ÜÑÂV 3°˜Yp˜e‘C 2@š2‘9’——3 Ó7jA@9ô$I4šfÁO FÀs¾ÐšE9®t4o1œ@ >Ô!‡ÂòJ6ƒÐ),býs w2:œàKÀ@ËYቬ� �@!ÜàÛ ›eÁ–±ÀTðÿ–ÂI“ûÿðaDs9´`F@ `% úé Xã |! ¥p ¹€a£7ÁÀÏZÀ%§�ŒÁRB‘ S C ]¹ 'çy"°�ÃPŸí©šò0Jñ½¹*¿rŸ:IL+ýâ&àPB*¤â@=úšÜ�!;Q—›1@qïQ.{É. €u ¥ÛI�a›Ý°£f!×@Lð/‘©£;Ê+Q¢ ËМeÁ2gº ôà%² Ò²']CžŽS™y uÊ]cGÁ¥ÜàÜàžý ¦±@¦ò¥hê“;‚W¢&åà–º™áP)bd‘)¯’ÿ „š*e”/Pv°Ñ ©ã'ÝЙÞ� ‡*£ñ@£‹Úƒ›� ©¼b)¡±0ä`$ÀJ$Ú'k*—9çá/× �{‚¤� …Ò�¹á¢ut1 *Fˆ*GÐnM0�š«ºJ½ªHf¡©ã0§~šÊ1ÂETú¬ÜàEܪڀ±º /D®ØÐó G u§àª’}ªB �a•nQ°hs¦/0.vQGñr²,·ò"*Q1Ÿj{=@Š6–áªÚ£Ú�­~3¬Ù ©Yb§949:°)© s©ùâ2›²<à±Ï` (Z1ë ™ � [ ¯A�ÿ—x­üb%j¼�9ìY²²*0¥Þ`ÞšG°µ£10[“‡“8ÔC<ä3:g1¶ª 8?`4’SCÝ�Ѓ®v5ÔÓ;žA îñÔ ×1?ùq›a%@�:?à•¹Q;ƒk; "· €C ”+C0^‹6¥“Z`FÐCp£¿!°a[º‰@v�µÆJƒ4´ Ô%ÄBÚ�¯Þð™âà¥/+Y  ä0kºÀ›—ª+°±EÃk8Êã—¤¼Ì[®Çû¢¯g!¨ä £úÕÙ¼Ú»ˆË%gÁ'™ð){#Ò»½æk#1p „জ«¾å{¾ð¿ò;ÿ¿ô[¿ö{¿ø›¿ú»¿üÛ¿þû¿À<À\À|ÀœÀ ¼À ÜÀüÀÁ<Á\Á|ÁœÁ¼ÁÜÁüÁ Â"<Â$\Â&|Â(œÂ*¼Â,ÜÂ.üÂ0Ã2<Ã4\Ã6|Ã8œÃ:¼Ã<ÜÃ>üÃ@ÄB<ÄD\ÄF|ÄHœÄJ¼ÄLÜÄNüÄPÅR<ÅT\ÅV|ÅXœÅZ¼Å\ÜÅ^üÅ`Æb<Æd\Æf|ÆhœÆj¼ÆlÜÆnüÆpÇr<Çt\Çv|ÇxœÇz¼Ç|ÜÇ~üǀȂ<È„\Ȇ|ȈœÈŠ¼ÈŒÜÈŽüÈ�É’<É”\É–|ɘœÉš¼ÉœÜ=Éžüɠʢ<ʤ\ʦ|ʨœÊª¼Ê¬ÜÊ®üʰ˲<Ë´\˶|˸œËº¼Ë¼Ü˾üËÀÌÂ<Ìÿ!ù,ÿ,±ÿ€>‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç� CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ'¥7@! šŒh&£—�ú\Ê´©$6 ¢¢yUeUß`zUë¥4 ˆ8K¶¬!¨QÅ62QuL2°QÿMlíŠ l³xó.µ‘VAšFaÂ*p‹LŒ‚Àj-qUàÕ’]½�#ÛD‹f0#´g¬"ã« MÔ¿ŠÉ�i\ lbɨS«ä¼¸Í¢1 ÎT&lŒŒæÌ´a™VÍ»7I´>l›Q´øM櫇ޜ8#ú hlÒ¸d#� AlÌŒ!ƒ†M¡7iž'ÿh±wÏ�E÷®> û4'¾£!s&þ¢èi2 /½:{DÑ]çy‚´�†¢¡!à}óÑwi…°ÁÜÿùfá…Ôp]ˆàæCeÈb\U$á�mS bFl6Fâ`Óù€^……À"#•‰1lÃÂcgEå]qƒœðbBÄÿ‹U¡ñ£"KÆWܰ19Æ‚gÆä‡dFâ�†)f2Àù˜\ˆ,éÝlßi)𡱤'ØÆ¥ ™‰±ä'¤�F•DÄh#"‹a¹H”ƒÀ•¤°åæÃKÞI¤œ�‘† {*ÂU"HÆ_q!òä£BÎy† l˜`[TöRYXf˜àg•b�ö£s¯¢9殼S¦‘§¬ lò†–ºòê’)ú°¢�…´±ä]‚(Ú¥::2,!Ì&×í ¶eK*�>xH‡Šc!?Âö!¶!>È»�~Çá«;2x‘QY®À]/b®b+’0£ay�÷ê¬{²7ê9�ãÒ*žH㵃I[¹•�ÝÖÄEE† įj߈ ¶ç�¹H‹"r6!v+{ØØä&RfáÆ;i¼ °Á•ÿ­¶nþù–=`çÖçV,ê™›áTbd»ëgÿ¸#¯2ù¢îÖó‹ÎÐeœ9E,z³Z!òÄ-:a�èÑ güç¿¶­*,CŸ7ˆˆ×‘.b=à×hc'F¼ëk±K þ|·B=bOgˆ¡ e¸ªèÍ q“Úݜ斈é âe‚¸—ç È>œÍðˆ3¤Øˆ¢’B:щêóÖ‚¨@……ht‰hš Jˆˆû¹ [r{Ĩá/BD­tLËÞ�ÁBlˆ¹“a2çÂtÑg?©’ßžÈÇóyÐPƼö-àâ[¨rÍ æA<:ÜgoûÛ¿÷GÍ9Ò§NûÏñÿ²»ýì³&{É€. ÂÔ‡3•‘iÃçÝè°FtΡ†»»ÜòˆoL¦Ïlš�ùôlæ‚Ø QèÊ—½ìFļPö³À+´Gý’|éA¬ðgß=!6°…)¿ÞË%/ÃÙ—_¾sšÑ|73ÙKïS;œòb>{»÷ ú1¡änà{äçüµ³Öày¬Oä×Gê:"¾>ˆÛbœ¶|÷içv§v�×wžvugzŸ†h°^±\°€rçv»Çi¦{ˆ°v({|çx>PÂWo[ÐndW|fdnÇgŽ6fíæ‚pk~¶f$Øÿd�—k÷}²Gv9èhf÷t.g‚Èiªw…ƒ)¸m¦¢'f0€@XeoàôWnÀi½grÈiç‡e%Èvô¶dg€H�á¶v‘–�šxFha‡|çck&v]iƒgr|·z�²v,¸mxwÄ7�^¡‘6k>°‚-ˆfwn€ÈgkÐe€h¾FAUAmt¦F}će]v}z¶bx�Ôx¨ˆƒÐ{µfraWr@6fV8f±‡hh|'Ц†eIXH|ª8†Å¶f’†‚qÖŠ|{kf€…�‹†`jqFŒ–‡€h®V›‹›‰à‘*†öh®±š÷Âu˜ ln†�ùš$›z€L¹~cÆg}I&¦�Žºi–qɪ‡±�7|)È¢ƒPà�mØ tw§Š8” šî¨sÑz¢‡@êç®Û†¬Ô—ø•zÉcZ©ˆ}Ùyui‚®F|¢H�y†h ûx&ÙŠ_g}Ñ u`t,J‰zÙµfG}o f®fµî†d¨)³y¶[=ø°ÁWªEŸi•h‚^pnniF×eWk‚DÛ•Û¶vb7›kÿ‘Ù’õ—\à� Kl~ ¸r‹cPhhaWzd�õ'×J³ù:­~J¹††«Õzª¾é´…`xfv¨×Èw ékçp[¹zdwµj°³&8µ•iŒ{zÿéø”R¨™–•9‘:§�çeÌæ¹7kÀ[»»ƒí¹­››Yz¹ç°t;üÇwê'�`Ðp` f×G{p¶úHl|Gyk—p*v½È›kÀ†@m´×Ú¡7)¹©ævç«r2X²W[‚h{m�{•ÉZ¡[§ì7µ†ÐeÛ‰¾Ç”àk~ÝJvWVÆç¥l§:xÁõëÀ¦§ˆ×øìÛcÞ6f-粓úiùëÿeû›´·cÓY�y»ˆ&©´Çr¦ö©ÅGÀÜ­ùYÀé»{w÷{6Üp†Çs×m <·LŠþ¶ÄÄ·Áá �ø®8Éy€'ÔWnÁ�dgh†'yK¡õ·ÄGJ¼&K¥llcLleLŒy¼ÆdÆsuú”¬s•ꈈz]{l½ÝªgÁ³­÷¹ˆZeÐv’w‹¼¢"ÙÈ};¹¾ËŒpn]_Ø|Ɇr:‡Û"©m'Grõ&qò&ŠLt[xp§—úm8mÓ±Ýy�_væÄˆ€,raÆ~Ë—sžfydøœoظfç}Í–wlŒÉÍ}ÞìfC†‘öi½[“ Ìi�kt§7ƒOè¤ŒÇæ·våÛÇ\Ó‘œè=àç°vqû²a첑6�¬kz¾ú�ÇÈÃHiv^�´<;ÈžáçP½:�ïM›[�$ýéhþ¢*Æ„à¦9˱®á.>dô £�'ÿµ5I¨çl–æ*›˜ÚÏív�†€á/äCÖâÕ‡“ñÉx# Èàºâcr¤KÕD.ä<ÌZ_æ�¡ §–>޵¯ ÉHÊÔwä¦Ú¢SNå:±cû5³Øw4Ž¢ºifK~®�,Ç‹×yÖã¯zæhŽжæ4i‚yväYþÈFëÆo8æ'¨¶ ä}îj®_€§»DûáYþʼnº¤¼xGâ×gÁ|þè5éùf#xÃd÷vÉåþÈæªé‚‚]\‚e_kƒ‚kŒ‚>�‰nŽ>“•Š˜še‡on`^`�š‚§Š_£›”­µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇȾ^ÉÅÿ]]ÍÑÒÓÔÕÖ·i ÚÚclÄ\µ[²k][[è[[´]`è>ó™ƒèê[]k\ gnߺzƒÊ¼s¤/]¿.ø\�‘…ËÍ—}ý¾¬ÙòEм†ìmYsÑ!º/o¹ÙÒ˺ƒ>Îcw¨—}ˆ¶dZYÀåÈ�![Ø*`Átë08Rˆ°PÃ~ Å{º¡žß©ãw²Õ†~6ͽF¶¬Ù³hÓªM{o­-.`ÜÊ�Kwí‰mxä¦�–£€‚Ô åC�K—´nnY%èbÄþÔ¸Ò�h¼›ðf™0ªÈX¹€Uá-JsÝ íê¢ËŽ"—IöqñÿãJ‰®ìèÆ P7)c¢»­/­7eÀ�Ëô¦*¾å¡hÖº sàs°}0%4ß×-³…/ö®nvsØ%½°Rs.µ ʲÞ|7¶®ýûøóë—Û%èýD´ß€x^y�Œ@³jÜ…7HIð@8È<¬¸ãß k¨CÔM¶™,î äNv�1˜…·ÌãÞ îpÔ˜?ŽÌDQ>‹¹ÂÙ ;r×#C èÃçèt:7êcÕ@!Ö¢iƒŒ(IT7½xšŠ>´§‰`*î––ƒðƒÏF:Òáõ¨æšl¶‰_áä·RšnÖig4Ù ˆ &ür”.e⎕®¨“ØÁeÿÅ 8B¾‡¦Tž9 @ ÑÂ�-º²”­�i d°Ísã<(:è‘Ù5·á9‰^tã ‚Ù\™aŽ%Ø�ÓEªÒPS6…h+. rS¢ƒ;¤Œ"½êÃ8³ýY‹;tÞ)í´ÔVk‹B¸â笵Üv‹�žàîÕË­*! §F�&meËœÂnq˹›ÐÔk¨�¯¾k”Ø‹’¶œ¨h�àÙðyƒ ,lœ‚\ú–‘XirQxôÚrμ#iG%ÃŽ8FÇB¼ã<N È ?êíÊ,·ŒßM�Úç­.×lgžàjs±¼¬›Õl>×Ò×@õeª¡µJE³#ãŒâ% ½$õp½�ÿlËFèŗť0ªŽâÈ`jÙN=rˆìJekòÌ-MkÜÊ"-Jg¦^ÖÝÊv>�} `6.øàÈl„´}+qMøâtá¬'<÷âoLeB'ŽºEI®-¼2ÙbÁÛ VÀ$“”Aú$%ãw-‚…šqºèaÄ‚¤ xíKCʲniõ-A·bõÓ„Ð9+Árü±Aeª ß4Ú¢2ãÔWo½ÆŠ×ÕÅá×w�ãÚðiLƒr§«¬ ˆ},qÆ+å~šeZ·]¥Gn¨Iö^å(4²m¥ë»ä›ŽV5pD®a"³Ÿ£êÑ9àůw‚¢ÒÝ^—¼�åÍcÏ“ÎÅj±ºyÿïƒ ”–J¥8…ð„ÕpŸ y�c‚É]¬à‘9|¸ä|.ùPÚ:”ÉA&uYz .ÔÑ(!k[YáBä'U� Eà`ï¤(«):b(ÃÅjj¡� ðívRDžñ2¦0ç9B0ÑG¶Š5=ºñ�z�ƒ?8Ú1ƒ¸ Çý¥«ÄRƒbh¸>ÑðãU3�ŸÃVÐï*CádÜÓ¡%qÑrƒO\¥ô¨ ²È›‹×;KÆ(�˜rþŠ´F5Qv'»Ü¢è¶¼2 ±|ÑÕ<xÇ^úò,ˆ~Vò¿_ÓlèÆ4|‚®‚a À8²þmBÿ‹(n†Û¤6(ƒ'ÒA‘ÉiG_E¸yF}n1“ƒ:ôá:¹(ÏÜ€<ýøµ¾mŒ•¸ÉöÄ ™þT‘„+Zä[¸N‚’F˜«…óÄ&<öñOŽ�± @È& œc�î;¦HG †²IÑ(&IWZ¯¶I*dsñ²T$^2ë¨NL,cˆu¾Ä š©+pŠ.¼J0Ü;c‘Šª†¼™Q$n`ÏK¸ !Qö �i\�Ú $b)QCÄ%®ˆÛpˆ£™ð- ‘!JX¢>E\eS&"�,|E¾@ Nè÷ #ÜødÖògÑDæÏô -H®�‰'ØL‘(ƒÑ_%Îàˆ¢0˜yº•Nñ«DBàX÷Å-S»õ¬§AʆóÀñ”~øej¯³Fº²%Å€EéñS»I¦8¨­HB䎘)Ù B�„¾„aG�4|¿ƒ&tüƒ¢�(8úÑ}Ò�) AŠ? ä³£¹Ýð±<ßÑ›´)‚ïZ¾N¡ÒŽ{{ýôš ·šLˆú²€Oz ÆD»S¼ö¦DûÎ%ˆ[ÐvÿV'ÆÕÚ¸9¦ÚtM1V¼ÍJûkÚ^íMÕL:¬"ÕlÁ'-†Õôae›ãÏùZþ ›hŠcŠFÍ¿7Õl½Ð47@KmYÉôS2št$jøãyÄûMßzÔ+„Þh±=ýG¯· b  ¾0vît%ò?¡Rã€+–c*èÀ ó�úãM‚>š–wê²€\B0Í@•=’â!Rñf©g‚¨812a1$,ñ‚-8È3.1kµlŽâXQü7€Y×UùñDø=9³ âà ËD�¢|õ¢Q;´0½ÂAè� îâ$qâ­Dg�Q!‘#…Ö”E÷ÃV>ˆ#a†’²ÿ#^Xmm$E‰Qƒ ¥1©ñ†[b!\…‚W¤"[èU`˜„çЦ Rn‚ˆ ¨'{” E7m%oúðlø²/øb5Ô“˜/•X‰Vƒm¼09ÅÖVf4"•È/ø²Aˆ× *cЧX‰ÆŠúU‡�°#æ÷kGò#²èT¯S…;%;�v�‰Øز65ŒÉp K¸3U“V´$GòWh‡$> ……$Ó¸£ ofVtcFÖ´TÙ˜êW!ŽÓ˜²ø ¦=>ÃmµÓC¸èOãDò'Ì0CˆŒêöh íÃ�Ò Œ�Óg$F�â5v:¤c:™pdÀb$ é�ÿAév 3µŠÊ#Gf„éZ6LJš 2 y:¦C:&ƒ ð¨#*Ryó¸#ÜÈ#*V„>øh‚e¶�ný‘ò�=ù-{Ò'Áð„)ùKõ.ø [µðJGã”Lù |“”zÓy°C•Âq5*ó;AæóØxWŒ4µ’¬(�M††�ÐO+8%‡�8”ɦ±•bjt *d™Jv“�¦�mAdäs¹Óv³D‡)T’#A—fô‡ò#jT 9T‹åf–39‹iÈ�‰K%éE¦72¨,M$&­0C{™u/$”«å‹­ y¢ÆFAߨ�ÿ£œç 7!™ ¥ÿ¯¤1½yç0 øÂT•ÅŽ�[¢šÜQL»DóÐJÒÙ’ÞW2!�]“jY lY?˜É0îPœa(³)p ‚PiÑVë9 Éä ï–VcdÜFù´OÅ”C6N#ô+Ž"ñ¤žh%QqVOUfƒ&ádqé%µàQ¨q`Ž\Ð�Ñ4˜ûè…èO7&·X_2YšÍsPÓÁ1»”O_Àú —ñk Áa×'…X£µ…› �yç;„7#2B*~bIÖ˜?±TQ¦lEÊ‘“gÇ@eL/�ˆ¸GTÛ÷tˆ¢òH¦Zi„8Ùv?uÿ ŸÉ£È6îI‘TnÚ-QDî7t†•4ˆu—öñW±óYCRX¢E‹€Š „ª&>æ †…gS§ê ~: Ñu�J8‘?t:oq8#“Ê8A¨ ]—jióp¤* £ñb:UªÖ#œ7bƒ6£�–8J=nP%gt4êª�vA°Ù FT­ã«ÕS23²£Ô’HæE6É8Õ ^"s‰¬&O¦AcЫÄêєں8ouæF8Bv­ý®Ý³PÛ3¬åºcÁVPðê np äJqz¯;°k_7)8µ© ˆN©°û°«­‹€ k€ÿ BYÂUÁ-²�û± ²%!M°v®E‚ˆ!›²*»²Qedh øÒ¦Ðù¢_�dÐY+»³<Û³§€Žh§ ÓÚ³D[´»h©À#NFë2CÛ´PÛaªµj�jÎz]0je ûÐ: ¨f¯‚C$ ûjùÀb+\ôjYKµj6µn;XÓöUº ‹± >¦œé�­Ÿ“¶·%›¹•÷¡�Ë—‘q›c#t¸¸ÀnO ˜V©+g²{Ï£%²G·o$J¸€Dà6�™ �«¸:· « 83G Ê Ø†£F$mª ðvB˜{ ùVo÷€„;º &ºº+àÿv•[r�°p áp,Ñp™À�‘N­àp9§Ž¥ r.� ')7s`ø #Ts*Å^(Ç)GXQæíP §°¯ ã¼é” ┣àp™Ào ¾ë +ÑÛpÓ[½$7(²h=[ï! çã[¾Aöt�rugu‰Àù`¼' þû½‰s0Wð5Á÷ ‡Ùá 4×»Ôû½K�؈Íh7T�g75�à0¾ ¬Eµ6«±òw a8á`�y«x\Ñk�!Q1rxw]vyä´‡=Q�»!Ã…÷PéUSGcwCR’GVGÄDüt;4ùƒ!VAœhÇÿ #¶vTª¦’÷­Ö$wg\™X|\�w"Ü2$¬»&œ ¿0{ùwNχ{™°9áeÌ‘á·B¹w}/¥“ä‹|ØGjÚÑ}3B6ÈY‡0 ºP´ðXÊ!2Ó·;ô2ìx}û#Ý…@Ûȯ‘²!;ÃD 31®…A¹·‡Éø€’[ ïç{'Uç`ħ(¨�F•x¸Ìy<8‹ZÍ>°ˆLh”»ð„ºÚN`"‰<)ªhÒ(Šf„[)Ø$²4g¢C%ȃ$©ËvÈF¹ÀD朅G"Ɇd™JñcF‰θ#®C3ô<(úˆÏ¥ o°]ä:#*uÿ� S‚Zªs‡é´µŸŠÍ ]-ÚŒ(ü9ÈsKRhmKÉ ?„…?Fì�dܦÐòƒXè ¶uÄlj0—RÒ)ú<+ŽÐ–{¬Yé S‚+ž‚™õ*ÓD�\óH®²“@ê”6ßÙd•ª…² “Ùò!Ö„3Ò ÀŒÙ棹?æ4£Êàm0#¸ru]–xŠsu–æÛŠ–D3Â)l£6(Ë9n­5i�€m»Ö´Öc)0Gí’j§“#¥ž!Δ؉ù‚‰y­‰ì@Ùž:½fD9+Ö í2‹Hf�Âê¥aØ K“CБ�ëà åøh瘠³¹–¼0Û*ÿ2(Òxl,Ö$О¡ÛfrQèR1PyI¼L·ÖfNÒ8�£Ð¤[ƒ�°í4¿¶œÚÜÍ-¯Gh vö)K©-ͱœ¬(LhFh’‰;·`’šÖ)lÅHØZR§ÒOh ){=Å Ú‘£fÔö�# ÂW“sŒ§a* Oî½™à,t*éÞùå˜,ØÝn'Ù„â�<Ï¢KRƹ`À*Æ9/Åæ ÜÕ�!ÆMƒ33—"ƾ�âQyš:"LÑ3ܺÙx¶§Z)aµ��´¹Ö&oëxø0ãľ ˜016–“ŒÇþ¬~K°y{Å0QDâ)øºÕÐÙÿ¼ú\ä“Üd9=‰µÀ¡+âR ÁI—LÞ† —¹“‚]*Œ@3¥ iøÜ ÝÅ13MQ‡ M·hÑ;Ř‚zÎQDÑÃ�kÞ€›åtAd76etQ;ŸÈ€”cTÕÅ5ðÕfióQžùÒ€¢+ú`¢ºBDØÙ$Ƕê@(Ê© ®žwýàymÈÂÑQ&@Žy£P¢Il·ëHA§3¥äâÅ�¼™Iç@-ìÃyí†WÃÆÌéøÀ§ <‡µ)áÁä.¸‰® �®„&ÏäP µQ5NEЊªêt4³QêªMí¡¡æíŸÐ$ÅÍ58… Ê!ÿN+ç�j�š>¯¥M9dšä4§OótQ ŸæËG Þ誔øT ºâŸ Qûi ó?³> êpµíN^�dïɾ·Ö`.˜å]f�FćµOëФxKN¦-�:U×0Ë%ԦغiS/#{ˆ"XökäœÊŽ‹RµTuAyíõ)ÔÎธ¤¾ ZõH*Žw?…´Ã=¿‚±£MöXøÅ0Y…hY�[»ùu‚u¨~ôã²§Á`Yª YÀ°ÜhñX@DzzY C'¢ï „õш¾‘雀s=¡™Ç×Öúƒ3'‘ÿú‰O’—ëûöA‚šŠûƒ3¯‡ (ËÂ3¹Óf[NüúÁ\ÝÚÐÉðÔ½Dýú!œ8/�Î_3 "!�`?#6äE¿ýwB^\­Åi ï~Gôœ2¼ /4¡äß2áÑ^0ò¢*Á³¡òó>‚ƒ„…†‡ˆ‰Š‹‰k`^\]_eŒ•„nj–›œ�žŸ„oš ¥œ¢^]\^¦®¯°±²³´µ¶·¸¦[k‚^ƒn>[o„­„[[‚j[¹ÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáØnnÁ‚k^‚\¼ƒÎ„änÇâ÷øùúûüýþÿ 8P R‚ÞóáNP††ñÀ,$H±¢Å‹ÿ3jÜȱ£ÇnŠ}I²¤É“(Sª\ÉÒP™_-cÊœI³¦Í›8©A´æebÎ|>v)‹¨Ð£H“Z[³Æ¨7k‚ÆRøi�¦RK1½jÊ ˜/„¬îDd5k!¨\¯Q¥e”ØYh­¦uõ¦žRFôl PÀç›.6½Ô»· 9Sº´-ù7°¢ \ß�\sC0…c®Uf§ [¾ØbÏU0`ˆ•ê²N�Ö¨¹,ê † ÂCk –t­s-.ð`aÓ…R¬Ë©¿¤–¼)@k”NMåÞRƒ—½ß„®´¦K²`0à²7¸§Ôc¡oW´œ²û£e6Šo«Ü'7[(©éÿw sDø5ô…l‚|áØ!¯-óÜ!Õ¹æß LEc-·ÅR€q°ð‡%ÂRF‡&¥¡mÀrB y6ŒŠdé%Q!exw`'íÅDÝ"è½§#Nj€¡FÅ·Fé]ò£ ©� •Ad(jÙ–vÆ-(Wv?®±�D?úðF”¡€èÃ�O6ÙÔ� e0ãxïÌXÁPZ!4ä/Q9 U“P1É'š�‰¦HCÎå‰@–‘ä�)ÙgCnD©É�Â8YÈ$„Dég“HW”¼³!C_Y*/–¾Q¦—GòE…#‰¨Àˆl¸²Å‚>�7ˆv`)òy⬕äØ~ÿ½&2áŽÌÎÄÌ…—-së)H“Œ&Œ#Á€Q€&ˆ–± xel`›²ùù€½°�¹À€qŽh™ì‚¨ŠøMôá(\P2¤wà0H_ozñ®… Ó ˆTË�sä­³ÆÓÖ£`™|… I]ŒÊ<å$»‡’L÷Ê÷/_`ìhĸƘ•ñ*!‚ìK0­°º^´gT2òâ)óaT<ÇEÌ_�,ˆÍ(É:«cÔZÕûPF`ò€fØ(>Èã5¦ ­ˆ"Ê^|‘A1eÌmNbHg0ç–q†` Ï»†l ZuA­ñ�€U7"zsÂ8xÿ¤„½ÅÓ•íŒÍúùó˜âÙ…ÈÉÕá™ùÔ¢ÍÙìêÞˆëZkex³²"uÑ�Æ "µg¡›Ì.’¢ñÃɼøŠiœžqѵROíiŠïÜÜ©öÔ^Æ^`kj´æÃèF;„D¿Ép®ùD!A‡¢[:ËlD˜½Ð‚©X2*!Î+ðÔ!Ã9È2ÄB›šý`ÇEEüñ6¬ù¼Ö5/Üf !›�ýð,G`Ë…–4F*ºÀšçüo`kR�­ÑpŒj'±Úg†t‚b/:K9¨3!ÞoL\ûLyD»xGTl³W’öb�ðs é—k¨Å Bƒ ÙÏ3½– inO}£qGÚÔX táG6 Ád_²Â_^ïAø)�¨Ò–»óq-I£¹Ž30é…b¤¦ŒZ (5Þ=EVÑZЫBƧ¾Áã âÿA—qØ…1Ch' ²ã%@$Æm='Y™âÓ‘IC€C2LÄ1uæ5M¨�n‹¬Ÿîñœ“†tI%!’/Ùï69KGª¿ÿl/XÈ3„*¯e­Ù…h–P%‚ 6ñšdx ¼JŸÚ2Rÿi0(ö‘±Ìh=Ý¥‹!tJ/W¾1Âo •ÃÎàºÑçŒSDDr#³Î(GÚ¡+!Òv Š45Ô›"<0¦:�:ödÚ/Dñ"8Bn8+Ç€ã†7l–q~+‡bt‡ͪ³$…‡ÁÀ"‰rHñ)r"0Õa¤Ó^×ê‰9SŽužÅ­š§=^‘>Ž(ë‚”ÿÁ˜6‘=%ä4 ݪeŽd­Ë'ö:ÇBD»,uÒ%¼�XB̑˧Ç'qdZ+Ôð¨g¼õ°�:4™b¼FQÙ Noƒ2ÔÝŠªNPéBp¨��œªvà&6PÂt­¯ìÑ]71¤’T¬ º¡!ðŠ¥'š\uIÙ5Å®1¤iÃ�–s"D¬'ÂF¡˜¨°hç8ˆ|äÙ„Ëãi°7bÅPâ­’q Ä¥›- \20– æM&�é¦OŒ,Aà鳊zéÕZŸHŠ^î˜BN†ý¬Jž.93 ¢‘�¬"LîØÉÆ‹$ä ®ã%ämÅdÁ[‘"ˆ¿›Hj,ÿœB¹"o‚ë Øï ¢f”•™b�1¡U÷|¹Œ)úÌŠaºÛÍùè%ˆx"�»®Iè�ÈÁg(0^0à"d€´šÆ8«% H ß®LK®¯cF†‰@áD„w ˜Ò¶š$rP´£""¾ žof»ö…ˆ’µ8=ÎöQ<›Ô–Íe¹H ŒÆ"[�:²è‘%"™ˆïNæi*9õSý ò¸ƒÃésáE\½Ijk5‚Þþ [’@î6ð>P ïÛæJF{] @pï™Ì>„³Õ%XC€ɼ@µMr¡L|e½s60I:õS]»7,ºU#ž¥Te1Åÿ:‚tã(÷õ"h|ˆëÚzú|—Ò—¢+•@×QµŒËVl±‹ˆ7‡‘w6ˆ¬|_ xø6„\–bD¤Í<^ùaÉ×~u¢FN4Á€Ý­×±Ó�í$É9-§ ë$î™ag‡¯)ÖXcÀCW ÃðÑ‹ÔÙ“…­pîx!òÊÍ‘{øÄ®Mpv ô=/äÅésÙÃÔÕkÎï¨_‰X²þ¨Dhm¹N}¼gÕ@Wœ½ŠEùÏ}½Vr^ùeÛl2Íï~h3 á÷uÄ †}¨W=?D=Ü Œa(úÛk¶¯ zݹì P”Š[�šŠËþüèO¿úAÁ†¬Á“‘`‡2nlaÿÐêà|Õ§8|€‹)5&~fUFô´A®Tj €•‡+„0�ANê¤W²z”7Sxðn’S QMTl^bLë7‚$X‚&ø V„u£ö�Uå#tc¢\€P7JÔ5›A4Ø44užG'—£‚ ÆDa1R´.H o5ƒò h×WuД 6aˆp“�6Dfdq‘g[ÑæcGV'[r9 ¡"Çr¥w‚l؆nx‚jÖMHÅy0è%r7BAeR1L>yc‡{ñÑñ-ó;pgcñ°‡c4k¸F0x†¾"d¶Cq/ølîÐ}[ˆ€fv+æ X€ÈooÿŠ¢8Š%G)±)«’i@Âzé�ŠFó—%$R⊕€} )5 m#2HRNxTªFŠÄXŒÆxŒÏpN‡@1äô Þ1Œâ`mƒˆÈX�Öx�Ø(Ô„hp醉ú`O3¢À’�æxŽè˜Žx9©`U¬¨ ø9hL�� #§Žø˜�úx‚Ž ]�‡ €_G ÕT`c}�_z Õ” FuÏà ²b•ÐyÏ&h¹qx°¶QnBptE­±Ö©Ôó,øW˜ïú«Že1Œˆ¬ñžCÉ é°¡¬&è£P¤8b=˜PŽíêÁ±L]7­í�kW±G±e¯ªs±��Z"z!¦±¹æ’gšT5³PQ˜Êˆ¢‡mQ˜�+»©Ñ¥üú؃e+± _ÄÙ4#»ÿIK‚ {5ôö¬)‡Iüñ r#T‹ 7r±ò:AS+¬5v¥�‰š‰P¡Tg²_¸�ˆµµ£êªd|^›0` rŠö^‘§¶£ú³¬Ã ûã«!ˆ¨„'�g:C¨ñrÇØ´h@™þ7ÖC�‡Ž‹ÛWiÈtJ‡†} yqÙ¶ÎXªkø…ïš6dh¹ïR "ö…‘{�§š®QˆÎ@X¤û.¶ˆ·Íbus•3·;s1$GØW!(¸[‚̪R¥¸b˜r‡¶Kä“{øÑ¼T䂤©3ö…§$·ªÛ�Âfq —0 ƒl›ë6>Ნ»aÖ L ›¯²qµ•´û¾ð‹ ²DÿˆšŸÊKkûI,Þá-föV©\ÕXƒ&A#·|Ä6rb»½Ý:y\ÈMà¦AÑÑCRöå+v�y$vD³q¼a"¿ Â× +aÀ°}—rq†¾’åz9+,¬ö{QhJP„Tˆ•¾Jõx®Ü»†Éw eÆ»ª3òFüF³5ülšû�Ç#ÁÒÂ�Uå³"<ÅTì í§5(¨7z¡gß1s‡¸¢fró5³FÉÒ·3xÓFÑha\¦”Ǻ!…RL²e{F¸%T�§ï⺱–�ée0‰„µ�½Ìð4 ôD†‚\Å’<É÷�¶{± ssaw47ÿA£†Æ…Àj†ØX,úD„ÃL…ŽÔÃUÊœšjü;wRý“½k³É§\²CnËJÉÀÌQ*’AÌœÀ(‹P*Rl ¨¸‹ˆB,‹Ù­R QbÏ,ÌØœÍ9Eë͡¸´p¦½¼ÌÇV‡™6ƒž0ä\>âÊèÜ ßâ áó ñìÍ?Göœ�æ �„Û1w[ Éà {º ×a°¯zÁg ¡Ô Þã ^`ÐùÑ�WôÜÏGÑ(WR–0Ð ýÏŒ@«‚™Ð’„ÑÒãŒ$=Ñ(=ÑûÜnMjžÐ -ÒŒPÐ/ÝÒ{Æ·ñ)Ð+-l'�Ò>íÍ \ AË ˜´ÎªÿÑ•ÀÑ’äÑCgÓ‘‡ÓbwÒ Ý 8ûÓV}Õ¹RÑž€I' ÓÉÕ´ÌÔý†Ð` ?Fí5;�ÓX½Ö)�Ö† ™Õ'Ö‰àÕ‚!Ó‹@Ó�ÒP[ÖǪÓ&ÍÖ€-Ñnír[-×U)ÐvMS†­ºN €-Õƒ]°�=ÙÞÙãÔ\ýÒHÍJ=Ó‹�ÃdíÐg=ÕœðД}Ú¼ m¹Ú¬-4PÔÚ°-4ÞQJ±ÝÚVUÛ­íl‰ÛASJ]ÌÛ¬½À½Ú28ÜmùÚÆÝÛ·’ÜeÙÓ�%”‹#K û�Ñ©pÖ�¡Õé×UÕÙ� 1âÜ‚QÕL hv{Þè�Þê½ÞìÝÞîýÞðÿ߆ØÍÚ¶hŽt±@Ç‹Àí¼Qýíué7¦Óнò¢Dù˜³¾'.NÒàþàá>á^á~ážá¾áÞá~Ö=vB}*µPX ¾ÊF7‡)s–íXÓû ¸x²Öpâ´@à¨}ãØ@dûuæ!ŽC¡‘ºT…¼^à-þ 6þDN ¸x 4ŽãPþ ÔÑä»r+'Î7ˆƒÞ’KzC1õà7í<û Mª³»Ÿ“[nj 7P¤7å’É·^™Ü#´ANt^:‡89¹z®b§“Æ¡8¡Ò8qcafS6��刀å¾ äà£Àæ±…¬X~�a‘9“S虑ÿ^®7Âpæ:Ãé}ã„£8çiRÙ{®9ª“^‚œ–e eþ]Nžþ2”8�ò�¤_ÖÁ—Qì܉±‡�báË1TžZCÂ@põ¼\UF?lzìÀÌ^ìW�ˆyè¡DªVê¢zÙ¢‰­Û­f³þ]² Z¢aA±lfªƒì(d�¤!qþù;t'ïø­îOD³|Ôs6 u +äM[8¢fPëÎBúã^£B\–³GÆÎÎa&$N†Èî'ºV„B"ìP~#e·èÎ r’MÞ!¨©^‚ ¤Ñ]‡0À!(tý =vÕÄ � ¯ �…àDW\É5îÀÿd.ô+÷”l:C›±R…L<ú�¡ÐAê áVVzòËà eò~£Mò�X -Z>¯jnçb?CÞñïK:F®õòa…ýR o fÁÑN áe@5¥XßÇ`›”Oúz�!5OD¡âòóФm¾lê¦VZN”ÐC òõ!ÞÔྎ•µzMm››ó ./¦Œø–ÀàBÌø[’Ç€o"kú*åBÜ íuka !«š¼c¶ F,>øš0õ.ÈãJtº=ÖVcÑ¿©�0/nVÍ/X"ÒÌðž]E¹Áj¥€2R±Ïñ!›Úiè>�Ü�©Û…li£ì]OO8ÿþ�W�€#�—�ù³@Žà\ ŸE‚}_[k>…>]n>e[…[†“k[]…[\Š“…Œœ >�ކ• Œ_j[§…o[_˜š¡¡ˆ“«±†`[e…»¹ ][Ц´\¾�´“»¤“ŒÎ†¯¹Œ\´[Ÿ‡Ù¡nÈ>‘¡•—>»Ú“]ÖËâ’ å�™´è†Ð†n°…[j¡�` qÊËCß6qÇR:Nü�eÖªV£YštLá$†…„ùƒ·Á㲓(Sª\ɲ¥Ë—0Aaðâe䤙ïb¶Tófš^ÀDÓy�&F¢…ÔøbÒÇ—ž>Ôxù"Q¥š^ª®dº°KÿS¤`Ê{’UÒ- UJ{ÏGDZÂ’qÉ`²íI‘kòæØTPÂIfÏm @7¥-ŽÞ ç´-qŽÜé�¼f—¿5XÙì–YÕ$Šœ^µb”“S¥\—½}[ë`c­!‡bl‹bÒ´ŠuâV(_«oË6’mH±À ò±³Ë)Ê“x™ä7'»¾K!ç¤|w0_ÉŠO¾<Ë »Ò~3Ýü¤G} bo)(�3Ù�u(Á× c©ªW\,üp±Y‚é©ndI:ŸâaT™m·O‰O¦Äˆ¢_h FÊ©!Ì"o Ì'#ÂVÂá°üÉ+$b Æ@>eè{trñ0D:Ͷœ`cñÕo÷&�%S !j‚Â×nLâ3.(�Mâ�Óó‚¬OhcÃ%ì…¸03°!R9”4_(ª¦«ìY÷‘È>£é6ᤈep±ƒ%'ëœÄx²^nÀ»1ü6•Ê¢6±`„äéåóè-÷2n̺v3×!I¦Û£Ÿõ9?@cmûí-‘øóÌUéëR.7RíjÍÜË.û•¡¼Â:+ï§k'1êÿ6ê ˜O2bæÅß ¿Ào”áÙ{@ éð¼,RÆ“‘ [ö^'Š4¡ôÝ$õ¯DÐ�µãî?YÛAÞ1 GŠU(l7±ØüÌ…¡»Ý$}œÑ?ü�‰äI� y\-„•#AlfŸ[›+Ð4+³™£Î(­Rh–z�b£!‘³D"�·(0 E*𕥓äðN¹€ÕÔá8}+ÑZ“6ÂV5l„IYNÛ2EsMj‚ÛÐʽ܆¬'†"Š>ЖMÂõ¿2š±Lþá0ƒ3•Lª G™ŠæRN€Á&ˆ]ëJ qT¥Ú˜™"üØ$õÜ"@K�!àhƒõ«�éfë„D’â]×× ñégV9ÊÌ6ðØL€z 1ë7V½¾bwˆÛÝöˆAª2=~GHÖìú´± tk… ?ë61éŸìµ×^ h·¹ÀQŽ‘L‡ê7,&Hl Å Ä™]ªqÓ(Oxá£�ÍñŽ“EŒÖ N…ŒËÿ�äO¹ÊU¾”�Ô-%Â*‰2XÅ9Í®" ȵxÒ£@Š,=Y×SÇ…�ýAÚ32±‘Êð'5ŽÃÇ$JK%d<3ûÊ8µ\™‰<²‹"Ãh;ÚÛŽu…T¢¢+O»ÚCñÞû6©(Ññ@—ºöºÿÛýîÿS°+á pwBâà39Âs«nTºð+¡#Þ_wR'Åõ–1Lî?”3þò˜Ï|ªòØ^´›gxHqÃÌŸœÑ¯Ä ¤Õ¼ê×ë+Zðdã�D=³;1á2Ê~õ¸Ï½îò „ÝÊØþ#ó©²å…áïþøÈO¾ò—Ïüæ‹]d½îüê[ÿúØÏ¾ö·?PBë…ûà¿øÇOþò›ÿüèO¿ú×Ïþö»ÿýð�¿üçOÿúÛÿþøÏ¿þ÷Ïÿþûÿ°÷8€xõ˜¿d|/�)’¢kfÿàî1<)#©!B%XH(6ãyû@\8‚$ø"·Hÿ*s4aå@·YVed6…îãW„ò‚Kw‚Bw Fy%X„Fh~i fÐa‘‚ý"*„‚P#2Èq4ø- HÎÆ=�fShDƒby=t„fx†è—„  dÀH� 3´‚.ñ:D‘R^R{Ív…A3{0!Uœô‚ (L„"„j|h˜ˆŠx|j¸† 0nNx2BQQ¡ƒ€{jðqHQØBe>PqQ‹ )À§ÈWcRð!)Ÿ‘�hãyÀ‰€Ä�Ñzk0*¶¨j˜Ú ‡ìô_¸xŽP©Çµ‡Ë‘z»Xo TÀ8ÿŸ >§…нBˆ»ØŠVnDZ!2=Þh� H³h>µCŽû¶ˆîøŽv×ˆŽ¨b�01‰‡á×r�âó AÒ0��2’ *v&62_"s"e…°[nô…jAMòÕ+¼�&Xu�{#sÙ`Â’0Ò`VaÑÐ ºC»t Ê@]³a)c¶1l“ =²=_RtƒA"ù)°XcU5 E�– ‡Í•2ØB"x�Nù”9%�ó¨ap.��à�ƒ±Šñ[5+op,n™€ 1)Ï3)ÒŽnàmùpÇ;Ùng•‡r+åów«ð!›$ô´ÿ–�–›!—#�zßT¤SŽëcC;b$2qS¨ÓTÈÅT¹À�uSÔÄ–n©qY$…¹U!w˜#T¦C–š 8šÐT˜üà‚Ð ¡•¶y›¾%•S©D`,�•ß’hAã ¬£]oƒ£ƒÌ" 0‚I ßõCRBޤ6Kæ__Ó1ÁØZga/-8uÃ\ vVxVD»Sµ¢Ô&�%¢cRµ Ñœ¬ÂÐ ?‘Oe)èNßFúdƒaXD.3òh¸™  z5'°›j�*1‰ò-Ÿ3.• P‚Ð ª]¤0朼ñ 7›wh6ƒQOUÿxn WÐsMÚE¯Pæ@¢ó=ûåk‘$Èåx )…I%¢-I›Kö'U8œ¶RØŒó0LšE Z¥Vz; ê Žx'(Mqˆ/~6YwC¿ñÐ�`Èu… ú §A qU'þxGÖŸÞTiah  Ð%þÔ^ÄAH†¤¶ä“ø§.iŒ1¡3¢Ü’z§JÑë õɦ-¸i�¨º 9!t¨9|º! {[Œðˆx¥¦zªH¡›Žˆ]úBC¤‚G$k‚Qe°%'(¾|9^‚ªz»/‘ÄªÆ Ô”‹)c?"‘Ùª´=r &™“H7ãPÞ ÎÑb�áÊgå]«¥¼ ¶S³ÙÙú àx–]é…k¢�¨þЖ¢H�¹Vóå3.‡¶sã‚‚åÈg稪óØ›+Ñ£Áæ#¥àŸŠk7�2Œ:¶ �RTä çÎ]ãCç²ãɇ瘥Z¾†*"W-j¹¼¡êUÜ 2¤¥¡ßw£ŸŽß�N1.4†Ž{–>€&�é À¥}>Icݼ]àŸxLíeÓ©hQg)¶Yí­NY½êÌÝÿêª"é2ë¹Gé#ˆé»ÉªŸ”4OÒíÝn'tHáÄkDï{Ä•‰­�Ç íìÕ í©"íbíÇGíÿ÷¯¾ Œ‹éI¶«›"P’¦—г’y¿ÉаÂîï-çXóêuë«Gïþºç\)­ð‰œc�ÀP¹ÊP ’€©�[ðwÒ“ßÜÖzò(Ÿò*¿ò,ßò.ÿò0ó2ÿò¾†a°éDaÆGè\ô@§RÆŽïRöŸìý¬_òá­ÒÊk ÆQNÿôMßôÉFõPÿôNojW_õRõ^õSöRßõ\¯õS_ö__öbÿõ]¿õdïö †öµö`ÿöYŸöoÿöO¯Þñ×À‘(7cðcE‡>0Vò 0FLº¬ÎŽôH®ô�ùê·-ŽåÈY%DÆ…½Ð„êêþø¦_û¶�}*½ÛQáÓ·ßû¾ÿûÀüÂ?üĿܣÆsŸÖ—üuQüÎÿü…¢�²#WÇx&†0ÈÉ÷пýÚ‡3_2�!<±(ãE X!ƾVÙ(1áÿ�FJñvPøÉÅ…­…8QDÑc@^_>„…†‡>_ˆ>e[e‡n]]oŒ–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²£n[`ˆoÿk¶³„‹¥n^…`[•¤^n›nº·‘kÁ�žnÈň_]kÞ\ɶÞ\Ì¢ŽeÞÞÍk_»ˆéŒ`ç¼÷øùúûüýþÿ ˜"ƒ3•�–¨Ë>„‡ PëÄeм-‘¶ØS³Y(7 }<åȧ]6@$$�‘— cÊœI³¦Í›8sêTµåK�-[ ¨)dëÝ–eš‚5ȨP \|¨ à.@'mZ㥆^–¦$T (5?£J¥ t¢¥@CŠôt¨¡/n …íiò�1KÚ ©éjˆËTkê‘Ô(je£‘Œ0`ÜI¹²å˘3kÞœÐi™ ]ÿyÔW‹˜á-Ã…Ú›…�jm°[¦¶CC*Á|ƺu²®_µVÿ¤f»KPÐ…j; ©ºvkCžC�&$ÑËÝ·É2¾xH�ÜnÀÀÐÆn‹ñÓ&59n2ç÷ðãËŸO¿þÀ£…ÜT$´Em" !ƒVD_`@ž!Fvd‡‰_�d°]#öp“^Žñ'W^Èaú¡–H^&÷!º\X!ÞòÅt¢ æE¿”'Q\t‘&ë¹w{öÁçQ�Diä‘A"˜A`$ˆÉ€Á…“ú9Yˆƒ>$N^™p 7‘D9c‚^øÇÈJPJ™ ““Q——](·\~mfSg#rÿya—)¿ÑÂ�.ápÒ#—,ùˆäNÞ±Õ0Jç,xøJ„Ê‹$á¬q ¥œvJs“1i%"n¤U€Vf ו¸4¸E�jrª_b*=dJzPH¥öwêš 9bë›K-ÅP-•®ˆ¢®à¡g*¨v2¨%*íèé)C¢bT``ðÅ^!©´©+\ËXUzí+AeÙ_£ëÆkŸ’¡2Ô¤&_@ W‡$¦«^Ýê†dê"YŸ(ê«™«BR¾ûKL�xEô¯!^vgb†èbíI*ûI¤ŽjÉ´Œ|a¦¼¥¤¡m¤bœ�®ñ€ŽŒÛJ+«b�Â,Ëâ—ESÿ‰ôÑòõ—ß~"Ùë¤w{úàÅŸN‚£†ˆ¬RÌtÜ FÔ„pôÎF!E«Wß"H5tj½Á´›‡HhϪ¬ñ!~Åšï�P3Ò7Q"v¡ëlŒ€uŽ…ê5Øi·G¨5±"-ŠË ¼lƒ)?¡íC1±~Ì =§‚ã’«â—‡Z–®:f@ášÑÍ5ÂMÔaI:žmúªáF*»Q �ìn[mç'lnÕÎH8˜¥°µ¶†s«M'|sÅ_ƒ{vµÎšÑÓ—áðeÌíEmïÔ˜eÆvfs�’m–¹Öµ[H×–A`3ψ.¶ûK9t„ Wõ«6=©Í� Es¬.ÿ'¨œÇÀ†Å˜ŒÐÐÔh|!»‰¼aߢ[z6 35d(b¾`7L±‰*T‹ÈSD5` I´ÄÀ© ž7z c¢†5¨�{j@†œÜðFutLwäžÕ±»7F9oáž ñ˜¥6¾‘�‰C$n P³:²|ެ�õùF{„ï‘£yäžö(H;&Ž; #±5oX’ŒØWÓ/EÒ'ÇPá„ Q¾~e ŒBWfyI@fÉÊ˺à%®1ÿ” Å�†P„^—PÉ/ÊUŒ-`C Xé["—B;�&43Ϋؒi"4ÕÔÙüg"�á¢'›³fUÞbæ9E NØÂ–çü¨Ÿ@áߨÞqŽfŸP�Š[Êu‘ÃÔ"R… ØÞ`”WÝÓ~Âá17ÊQy•AWâCÕKšAÍ}"&õDI9!RTÁÈ _ ]G-A9eJ0 'øDó…X‰–x‰‡gH `3 Ç Ägìó eÐv%÷Àp†‰¬ØŠn8+·ˆ¨ 2¯À<È0°àtaÓl«`BfãŠÀŒnˆ2 ´è ¡Q<îå _ ‹§p ¿(ŒÒ8�5X ÌÁ‹ªpŒ²€j÷°‰±àBR³ŠÔ8ŽäÈqóÇâøŒŸø ž5‰M"‰­àE„�åX�ö¸qçØؘ Ú Ïr6“ˆ¥éx�y�"–ÿ�¥È ýè "�ü¸�¥ŽóX�y‘)/Sã A‰ž�nšÐ�—`}yx 0 ƒQrš€0,åŒF9ô¸ Ž˜‘4Y“ô!w›p´’–�k™ ’c†‡+ìÀ ù¨ ¡� í(( ‰âqXgç çc“TY•œ�“øâ Aä >‰ @yBÉ"ÓA‘›p”™@w�—€•i‘ó–V—rlù °, °w—àFaщ£g'pI“'i".Égså; ’gâ–(Â\P’.!™sY™–Éu™ V´•ŸÐ•¬P pY fy^Q²„ᘥ0•—¹š¬Ù™ù ž¹  ™ÿŠ-Æ ¯é ªÙšº¹›±p›­ —­0›¼0šì‚š¤ÐU¼™œÊ™ ¾ù9ù œ³@œ®Ðœ«�›Ëy�ØÙ–ù›Ù¸Œ­ �ghœ£`�ÙYžæyšÛùœÞÉ ài›â) äyžòYž¯ÒRöyŸø™Ÿú‰*G±ŸþùŸø‰Ä êŸÆ1%š ùYŸ Ú ö 4ó¡Ù™yúW¡z¡š¡º¡Ú¡ú¡ ¢":¢ú—z¢—é;nð*ê;oÀ¢* £1:£0*£3ê¢+z£:ú¢:º£9Š£-*£,Z£- ¤9*¤EФHZ¤?ú£K¤Lº¢6j£MJ¥Kú¤=ª£(º¥\Úÿ¥^ú¥`¦b:¦dZ¦fz¦hš¦jº¦3±;¤È¦p§k„M… Í‘rš§Âˆ¨Â8|ŠœBCE¦ð¿"R……ë$¨*×/ ʀ*Ä„’@@zZ©÷¨*+�©‡I ,I âñÍ8“ÑD™²0ñð3Bª`ô”âsSb©°j�R›ªÀ˜Ÿ$š°‘ÿ@3xc4ḠÕq ¸«Ä Œã@<ãF¤6jЬƒ…„‡4¯QIº­Ëún!I{VÎq<`ÃIŸ·­ˆ˜HMØijG´ã>Sg«Áz vS¬ðj¬A±Ó^°`T– „‹�ƒP ð…@r¡ µQë¸ÿ@Ùj¯¦1Âèu¤‰Úƒ s>ûóQ¿0#”‚8�TŠ�†¯òµBˆ0Š¿š ß‚èá8ñš²˜(wÞBjÁ/e™Tº€\0.Á™ Ò ¸zpߢ6·'u¬Ä«B´´‘WR³gº±+w±ŠP³¬—#é1¬*[µh)¢!(R�c4&Cɨg"ÖÐC¡¹³N㳦u<µ�K ’í 'r‚€ªrŒ#·«XKKUUTkµ|«‡Q �³šÙ}j’дâc²Og fK S?ú ÁtN¹Me’¶Ÿ@�*aš}ºcˆµm‚‹ 3�X"{EXBÙÿ¸ ò¸aCé¦ GAL¶Â“DkêºTìjZNò®¢[¼uxp-«µYb%j Eº:²÷ãùJerѪWƒ°Ôë¸h›¹QäšÂ]pˆÇ7ts¹–`%³`¬6œicãvo+Æ[¿sè=åðB±“q#eBJÚsXܳ;òÀ³± „·ôP†ÈS@ UN§@�Q1¼cŠF#¼Ãù¢¥ø iY`³Á¼ó K)ðb½Zñq<Æ-Ì0@&ñ´xj¿4\§‘M�B¡ò„|Äð/Õô['ÁE¡Í[¡Q·ý1�]çhÚFÅÒ~r+#%­Sÿ·sKÅvÿ6ú÷/µ0.ç1.Õä1×:&Qo5œÆVûÅwœœ°rñð1û“`x;w¶ªÆx,§øy‚G„&a½»’ ›Ç„¼¦’Á-Mò>…¼ÈŒ< Pr*QÙÈ’<É”\É–|ɘœÉš¼ÉœÜÉžüɠʢ<ʤ\ʦ|ʨœÊª¼Ê¬ÜÊ®üʰ˲<Ë´\˶|˸œËº¼Ë¼Ü˾üËÀ|Ê �KÌ'”²;Û7 ‡É�ȪÃy¯ršÁ<ÍÿP*Ì�/×: MÆhÁ�ÍÈðEÊ\¡ïIÍèÌ Šj ÜI Þ!Å\ p 6 cÆÈÀµÈéÜÏQ.£�ƯΦÀÿ§UtÎI�·n3 ê“JþüÐ5Á�çÁÝ2Ãáz|iѤ°˜ù ¨ýѱ”� 8ÏÚ|uj !ù�‹ Ò0½ÿøˆî†Ð+ÙŒöªÑ¢`3w,::Ó@�”Š�Å´¼Ê÷ˆí …)ÔNMJG /� ÁÐÓÉAia9‡éOJ§–éûž[¬R&�8¨I’OÓaaÑ0EÙ›Z'Щ Ž`‘_ö¼›žÀ3²©ñY–›jwÒ|Öº\kŸÐ"ÄÏ"½ "‚?ŸE�D‘ ^}&Ke‚Ö�Ðײò×&Ø �Öž€MOI¼ný 59�m‘œn‰×�ЮÖÿ sÛ Í qÍÙýìÙ¡™Ø+I) ±©s] ¸TS4Ã¬Í 0=ã Û�Ͷ PÛ¶�θíÈ´ŠÞщ ÜŸ )ï+-“mݪBÐRØBÛëݾ<ݲ`�ø Ý÷pܼÛö|ÞèÍË„Í ¼ îÍ ð= ˜í�ô]߃½oçRÝ©°ß³ÐßÛ(Þ£ÝþËê ùMŠŽÍœß­ ÿœþà·ê  z  âç1â n¯&Þ ¤•â J�Íá° Å$:ã4^ã6~ã8žãjx0ÌÑüã@äB>äD^äF~äHžäJ¾äLÞäGþÓ=åR>åT^åV~åXžÿåZ¾å\Þå˜è¦‘ '�ÍñàåòiJ…„çYmäFc­imþ .â#Æ_Ã=ãFéæmÞYnÞ ¹ðç:è †ÄrŽ>³ô bÕf^™ª²gªbÓ�…´£0FŪÅ\)S!la”7ájÝ�^réúª(| oÀ3â1ÓOÒ<«.Ž=Ú×ö;â6MÝèrÙl·N Œž½BÇÖTq6�Šó�2Ü K̸¿ž¬”7! È’cù/%¡jPŽë4©ë˜1í9üõ•ÒK„ƒ, †ýÃ_#¥^p�,­™âL¡9Ô&ò1ü¬í»é}uán\•ˆÇµ,Rý´ÿ·7”l¡Èm´w9]´]ì"bªo±cáðHi|!añ-yGÐÇÛl öú½f�» Ïų´¸¡>1‰²�@çà‘ý?í» øªj¶öâøN“óZ¾ºh [–Çï»3•KG).»qáåӓĘOûìHÚ¡–Ž÷Ê<;�×õdQvÅØ»ã%‡Au¬*R’c+×G�X‰XR 2ÕxÃé½~îíñ1¼³óÊénƒòÕ³[q%’âúa³ÕË uêíÐaˆì´ä�!8.ò³…"´ÌðkV|òŽ@;}¬ âÜ£"“/&êÿ5 ÃreÏDÕõ<ÕØÎ¸<)øs©•m’÷ b²a†`ëVçºVDž!1Áa,V0+äµwaj; R(‹6V6Þ¦ ‡Qú%‚î{¹ìšÉ6’B–àÜ©™ð´Ìø»Ïûqùz¿Ï½I:¸Dü*Wú€àã³&hèãöŵ€qøé¶¥¹µ&æõX9Xxx‰XPÆ¥yÈùèµÕêú™±Uæópèõ)è…ª« ‰ëÖY¶åÖé6Ûœqìl¸ûì�ë-ý+Œ-H}Lüv¼Q½M^n~Žž®¾ÎÞîþ/?O_o�Ÿ¯¿¨�¹eÈ_¦m_ÿ¾@ãÅ Œ¿C„©q„ƒ,r’”1 5ð�AAŒù(Àå�DAµ<^3ä«ãG_�ִ겥ʅݠ�3”�ˈ�„‚†R¤.7�`yòØ– ‡À”V@æ?rj&v"æ¨P~\»zý 6¬Ø±dËš=‹ö˜-An¸l´f(£!5AWýLúðã[C ›>DôЛº‘¬^Ä„ð£ bʰB i-WRZ‹'ƲøEf™.Àüä8 ´ÅÈÎH}6ôeë£d�ÔpѶæ´¦‡øsÉÓ]p†¸°ôí oA[öva©[8¤XY9Žä3­ôéÔ«[¿Ž=»ö핾”ÙÐ%úšÍ³Ê|éÿRæîä` ”)ã”K5oÊx¹D Ã,7ï‹¿÷èT2ðæƒKÈñ·Áú)3^L>˜'_V]l`^YeòÆNÑFÆ74–DR˜·” UQ˜Þ#áe°Añ·™w§a$k$×h>Ä8á{“y±V\„¸á†mÃYš! pI¨Æ¤j£d唫4é—3Ïm’#wbŽIf™fž‰fšò¨Áfz�§FnÄùŸ!ã5'Èxó­gll^Âç{oðg¡o:žõáùà{óÑWà|rJªÆOsη¤n¤Öœžj\jé£Kú)4´ÙÞ^z~ö^›7¸×#Üÿ±yäh¡-‚ž|–r£^HÅ]QeZà°-©ñE0º#±‹ÝÛ̵ԅŒªÉm·Þ~ n¸âŽK®3,Dœ�é`@¥CÈ=¸‹ïã†\ÛÔ]9ø¾ê̾÷Æ;Y"í–KpÁŒp /¬"�RnK$ ñ<;VÜÉŽ¹m£ñ9ÂnÌpÈ"�LrÉ&Ÿ|–DD™b";¶�‚rÌ2ÏLsÍ6ßòNòNÜά``ŒsÐBMtÑF�tÒJ/ÍtÓN? uÔROMuÕV_�uÖZoÍu×^ vØb�MvÙfŸ�vÚj¯ÍvÛn¿ wÜrÏMwÝvß�wÞzïÍwß~ÿ xàÕ‚NxᆎxâŠ/ÎxãŽ?yä’ONyå–_ŽyæšoÎyçžzè¢�Nz馟ŽzꪯÎz뮿{ì²ÏN{í¶ßŽ{îºïÎ{ï¾ÿ|ðÂO|ñÆ�|òÊ/Ï|óÎ?}ôÒOO}õÖ_�}öÚoÏ}÷Þ~øâ�O~ùæŸ�~úê¯Ï~ûî¿üòÏOýöß�þúïÏÿþÿÀ p€, ˆÀ*p� l ÁJp‚¬ /ˆÁ jpƒì ?Šp„$,!õ!ù,ÿ,±ÿ€>‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç� CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳç¦7@5}7´Ñ+&P¼ITÔ§Ó§På(P I* w'00:gªWK ½™ªÙ³7Mx­“ɪ;Zÿ��ê „¯7Ä’EË·/L1ZÈi«àj»¬[Ýe�W�©bô*(ë·²å‘vÈN5lÉíḊ¬LmØq—’´=XÈ W*9•çE©$[*ÂÛ"I*Y[‹³a)GŒ„„ À¦�Æ&–‚¡ÖÕŠéc‰6®Iˆ~N%‡Ž†då#`Òè裓HU¢ p¹ŸWs$uÂQBY˜ w±…ˆZ‚Ö逦m*�™�‚üvå'Ø’‰bk„^Q(¡SIw`’ZÒ£ÿÉ:•jé†Èªº¡{½Î‚¨>¼ñmIe�ƒÈy%S bí±j;U† P+H›rdŠÂ³âG[!6 ã�ßr7—(˜ • �,ð#•"•º„ )G°<úh\ƒ!"UŒ«2ÀpÿWQ:ièj•òIH¥`‰u%…«:°á•ëõèñÇb*‹H�ùí8\‹×êÚ𜄤:é QÂ9H½R­|^¯Kå*!Šž±¯Êz;Ȫ�,õÀ¿!lcƆµ!©rvÛÇ‚ òÛÖ…¤úàµ,2XÖ\Ü‚qYG#²ØÀ!òF”.òƬ K¥j…Èi©!õ†'¸Ï²‰H¥é¶8ŽӹРKÅí]>Û'ßÔœK µdÍšl!³rFê!¡î:Õè„d,¶m Ǫõ‚ùÆ!dW©í«¿°�†:bß´­WíÌÇ×�ÈoÜÊIö\�ó ³åMƒ‚ÊÙÛ¥T� {çà‡/ÿl‡tõ»䇶Yäë Vú‰(ªqô‚çÄ�Àÿò÷ÉG=÷#ô¹–ò"Gµ�­VÝó�Ó®6E@�g”ؼªÁCà�v�ŸÆ6xðƒèR•Á*xˆõ g9ãŸ~³3»õŽ‚-,DWD¸GX„¬Ã»’ˆ1"Y“°Âµ´TÃñMp>ŠS »¥ÂDL\ILÄ]Ö=Zñ.+»–b¶Á.ʨ^xòQì^¨À�l*… #AƯÉ…«ŠDñ3-"7”ÀŠˆ¾%²‘Ur¢\êƒB÷-"PŽ™#ž¸u;1)Í‹�lÍ¿Î@ÉJV²G”qã!~£+û Âÿ€­û”Û(ÊC²;Èc„å,ÉJV ˆw¼‘$vG@é5/”¢NH¹M6N9©tÛ‡ÔÊbÂÇþ:V$—é-≃ ý"§«¬`í7>«Ú"D÷ºfQ…È�„'aFØÈz-ºÁæ">Îf{ÜkÌ)Sx>Cd0yÞ$DÜæbp)8ƒ8Ø�§eJr’†ðçƒ(¸K¡�0˜�oðpðo`}IO¸5ø„>@p@ß·„Ôdž@(1€‚FHlƒ†mø€`8>PˆqRˆ%s mïg~>p‚8IH„€~ƒàƒ°Løa±?hp‚sèqðoHlè‡p0„‚Ø€à~ƒXpˆ%™•ˆârŠ$Õ†‘X}ƒPÞw�h„¡ˆ`8‡�x>ˆ|>@}jè£øŠQ¨Hb@Q‡0‚¸hª˜‹£øqÜÈ�/à�¸èƒ€ÿ�8‡¼ø�1€ŽÝè‚@}ÍØ‰?éçŒWˆt!�Vh~>ø‰„í¨‹‚ð‚邨؂�xu8‡C8�/¸î�5X‚óH�÷�cF åW‹ãhþ˜Œ½�q°"9’#Éóׂzx-8‡lH’.)’‚†)à«H‘§e‡h W¸‰†�‘G�>Б…p[X�†�„sø�ÆLy¼¨†8ÙpQ‚”à�%8�Šè‘£˜„w˜‹8ȆRY-)8‡u¸‘…Љ)ù”1P�ø‹S qÒኈ”$È�ƒÐ‘’8-YIˆŒK¨��(åç“™’ƒð?ðÖ'ÿ“ƒp–ƒ@p0–qirÔˆ–¤¸•OéB؆tÀýwŠ”¡‰?˜)¨–„À‹q𙈂�¸™rÈ’y“•™rÔˆ—„©™By„3h‡ÆÈ‹ù�pp�ƒ°¿ùƒ”±—(œ§ØŒ·it/À3éðKñÓù†Ih€†�‹pß�U§‰º Š˜hžr'‡éwlhœì wÂ9‘ˆ9Ÿq÷_0q0¨Ÿ: Z z š  º  jl0¡„o ¢P€Ù G—‰}@öù¡zH‚˜é ר¡—}u•[Y†ù þ‰¢'~&À¡yÙÿ¢-’•¡@”2šqì']’À£ø¢Õr�0�ˆ �øÞišôÇ‹É8ˆ€ �=:q±h•Ž ¤K¨£49ƒ3h††@`z‚×(™9Ž�H„Œy¥99Gw&‚8Z‹ŸX†8øÈ Þ)¬ù€û§�Ú(.w}w`”êù}šX¢pºp±(FU˜ˆéø”Ì·�C–¸˜™è¥�©‚ýx“˜“ùÙ¨ŽªH˜õ ù‚)©£çxš˜’¡X�eª„@ƒ6jªýö¨Ç‚�‰¸¬Àú}w)‹™¶™ÆHƒ©h›ÔP�ºŠpö˜%AZ§Õ¢£Hi�Ò׉òYƒzÿ£…@¦(ø­ÓJ­Xr‘“À¥â�¨ˆÿIÿ‡¬¶)¤Pƒéy®ç';Y \ºˆsŒË „”ˆ-X¯¶™5˜«ú:o×¢¥×j7:*™æê‘ÞÙ—?ɬyy“ЃA)ž +ps© @y‹sø5ا‚ ‰€‹)•:X¯Šè•| ![rìj˜/Pƒð™tÀ†ÛH2X‚òG5'*þˆ²ÅÉÃx³$÷¯žšŒ¿�Áù‚?€�I;”¶ù é±PksÛ¹#Ó9˜�žÒ¶j»¶lÛ¶nû¶p·r;·t[·v{·x›· À°z«S…Z±ýG™`ð®� |i‹Z¸Ë×ÿ ý²â‚´(HfÛ·µ®…�‡·˜¡‹†Ž›[››™ ¡›Šm|H™”;5|»‡½™­+´pð•‹Àœ[š°ˆ@»¢û‘cZœŸŠ­©K5Ú‡ š(»�Xƒ9ºLq¸ðê»ØØ È;”-J}“û»#~Óä$šƒ|»…ð¹x¤ùW¸NK€tð}1�o£/�€@ïÉñ€„ð½Hª7ö­\‰?@¸‚P諾‡ ÿ›õG€}z`@ À~Y€ÔK…r½Œ@”ñê©>ˆ˜�I„'ø•Ì Ÿ68ƒŸÉ§Jûjú›FØ‚©ˆ‘Ü›– ¦§©»—ÛÿºÇùÂ.º‘Ô§ƒ3ˆ|¤¹˜'¨²lÄ¥Á-a�Ë’Ùˆu¨‡™„Sj‡aáµóú�ÙøÖ×–mH x‚Vê{ ¨_è}‡ú¿Rü¿þ—ŸIøÞùPƒ5è¸ ²pY”>©ƒ)ùqHƒo¨˜«JKÃFÌÕêT‘j(éˆaèK\³â}â)–ÿؘ\œ‹/œÆâ»­› É;¥Ú8‘z‚�ˇ¿õ‹Ã·:‡`X¢¼¸ž‰é“œK“n9È/Á«½‚ˆàJ¸¼H‡¼�I³†Ð®>Ëǯ\l¼›‹éÌv¼‘ˆ�¢¬ü9ƒ`Û‰*«‘ÿ(•‹jýû�²KÿË.aË©ú¼h}?˜Ýìu•ÝØÎ�“ºXdž@}Þ)°…à˜“¼‹“éÎÝȆÆê»lع„šÈ(Ï·ªÃ‚œÈÝ»Š.[‚ Î&QÈ}³¢Š ‰h�¬ë�¹ª ý}� «\,¤Où”Í�K±­ÚûЫɽ}‹ÎܺM‹ÿˆ§8½m�Êg“P¬OÙ� x“-ù’"¬ù,™¨+™ÀX§’9ŠO9ÔD “‹è¿…À‹,=‰YiÐCº”‚ÜÄÙ¢Z,Š9½…8Ÿ¶¥Òבû˜ÁA‰Óe¼¿šX±>À‹Þ£ó¬»Ì)½Ij}þG%qì‹0–Ý<ӄЂ=‘„-ÿ®IYÅc�üzÈJ<™S«ŠPü©ŒªÎU,‰o°˜3©ÂuÍÔx­»Êš–U,™El�ьȌ½ÕÙª¹©Ê]½Ú%ì§w˜½�íØS.™Ð‚þwÍÔÇŽ¹XªŠù¦û‹Ù0�hš‚õl»Åí‘ó¸ÃâË©5K¼.è¸58˜QI˜‹œ˜)xØ™l®ðgÏ }Û&1²œ0ƒÙ¦®ÍšŸ9€(8Šøì‘ñ(’`­# ÚN-Èì š­ÜŒ…zŠt°î  ‚ƒ^,àlˆ–-øø÷š¼èÝ…ÝËýh~’Iž‚WMÞ:Áš·ˆÊÇYµØÙ¼‰éÂm¸Ûý¹ÿ›6¼§6øÕI®.ˆ�ÏKÿ.lÂ⻪.~DZ]Äþ¨ƒ(ˆºþ(àÉÒ`p�àùÖWΜ0äŒpäÏwá™PŽä� �8ä�DÏßyÎXîrL(áꜚ]îå5Ø¿÷™…csëŸ=«¼iþæpçr>çt^çv~çxþ Z‰ŠP¨šç7„ßlÉ— èõ¶º7šÎü[ƒ…n輘}!mÃ;µŽ^o4J~Œ©Òâjéùû7FŠÌšâBÀÜÀ Á—¾_R¤‘À}JžÉzè©-\„œ¦6ÈŸºH¢Â ¦ƒéD¼êú•¥t™‘ã­Ïßç¥ ~‡T¬ÆY˜�o|Š»î‚ÞÉ…ŠÈ¡ìæÂÞE¼Jÿ…¯n~D)Ì^ÊÉ5Œƒ—ÊCh}Œè—½ŒÏ0ËÛŽWâܯÐg~ÌÌÈ“éËñçáCÓ”©Â—j‹†P�J﬎ª²´%k¹>`¹®ºÏéèÎþÜð¾‹Ì‹èÚ ø®_þWnð‘dËÑH§A‰ƒÃ;:zÒ™Òï§ÓžžþHÅm˜á>;}ÖÊÂ'ïÊ‹¸‘PÕîšlÈ|ÅúÕ… Å>ó^d‘6¯YÎÇ®£ÏûÖ}Í¿Ó~Ùö*ÛHŸô�Ê“[©–±lò9Ú°›’¦}j*ô–½´�£�žõáó°|kÁ)¨¹^ Ý…ýÉy æ`ì‚hïòæg¹Çîö]dÞ‘Þ¢I˜ÿïÝÒ­¬ý„5XÊü}ŠŽ9ª—Kám(à-øÇF”›m»S‹†/âóœœ@ˆÏißã.xƒšïnL¾e›«V¾ú²?û´_û¶û¸Ÿûº¿û¼ßû¾ÿûÀü¿ä¥<À0½ýù† ‰«Ä‹Ë ˯’:è‘¡2?ü�è OÕÚ]Ä›½ò‡0ãVÈÜw­í�ð¼mʼ›�ÚØ?Ùf�ßµøºNýáoË`ÏŠ þ“`p1>…†…pqp‡ŽtpƒŽ”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µœr ru¥pމ‹„‡‚Ä›ow q›ÿ‚ʪƖ‰?—qqÒ¶ÜÝÞßàáâãäåæçèéꥸ¹¹¼¢opŽ_Œ¿ŽÙŽ__o½ ð0 Ð!t'1}/ÀPÍGà à€Éì?-½ÉÐm”¨ zQIå¤C1èLÃáRÄ™,}dÈá�ƒ¾ÔtC"˜Šë’*]Ê´©Ó§P£JUÊÀ�Õ3? ø‘ïÚ<˜ Å�¤ŽÙŽ…žºSÀ쳿|5ò±œ·g ½€ —k¡±få &Xð� (ƒýŠçK¥ŒoÛý�wRoìÝÅ[€±ãŸpè6; k¢»w/O]ͺµë×°cËž�©�ÿUw!n|‚”Óc0>ø-”ÎPÆ?о�¼�=hoýÛ6ã\Á_ mx›óNf8/’ý½«œù¡â‡¾á°hØ4|�‰6ö�²mçB‘)#]tß·½ŒŸù¢ÈPlÁA€!qü Œ/ŸÑæàƒF(á„VhÛmîÌaG'¿RK¼ÒVA[cHFCmcÏ\Ř5—y‡ìu !j!øƒ‰… ø×qd…Y8²0‰Ðáˆ4�Ã`J‘Ð%x—�>´ÅÒ_9×\…\véå—`†)æ(b˜ žX#"z=Yˆ�ÄáC –ôЇ2tZ²È‹wR’Q‹‡Ö!yú`ÏPò½ÿÄdGúòÆ3n“ %Ò06£Ÿó�¶¥!E¾Ù㘠†*ꨤ–úTUfæ" Ÿ´ ¤†¨ ZGV¾í1Ø XZ“PC©œ“Ròœ“ÇX™+®¸JRe—d$"rBÂbqsU:®ºÞê‹‘‚Z²¤¾ Z–Ÿ ‡¸¦¦«îºì¶ë®%¨b(†¡ÈåÃ^F2¢�Ä�Å׿p¤Vˆ•ØëC£G*Š’¿Ãœ¿>;+~<œ4§5 p+Yâ´,’%}oý�ï»(§¬òÊ,ÃV¦sÐÏ|(W3'2²ÁÎp°³ÏsÙCˆ ˆ>ÂH![U’¤“YÇóÓ=ÏÕ¤%mIÿ<¤!BƒvòÒ{5Ô ]É^R¢ëÃÈĽUgËl·íöÛpË/̺•’tÒk5†å–Wr4Ãú²é!m]× %KóZQßsB³6Š*†ÖÇ®‹ÚŽh–Ò™&²!˜c¶ˆjq—núé¨Ç�*V¨@’ÈÚ…dc³X¡kÊ+1úQ²—Øx'ÜJŒÁÎéæÅ gäɉ4xhoce% ¾ª %�fn%_mPÀâȧîý÷à‡êuPY c‹ˆ+ˆ²-þBîvB“HÅ� ïõxŒd› IYV×vÌ"1_ÂÜÑ~xÔ}¹ 40Ñ2Ò9±9NÝÊ›ÄÇÁzÿðƒïÂËeNs©5Å�>kË�,Ç«€aDrŒ»ÃmÈkË Sx .bt€a[�˜ä‰ë-×P "Þòˆ&SNÒHò–â” {ôx”É8`��ð‹` £!¤Cê fx1‹ù#Á˜…~BZþ ‘86îÃ��1Ÿè�FšuZâ’VÂ$ظġhBbX^@®Bì‚uz�  Ç1Zò’˜Ì$lbÀ‰#_wà#HºQpÒ“be%Yñ†„'¡´Õù8PJMÚò–¸Ì%Tñ9ßÀJ—À ¦0‡IÌRÜá-'»×"YÌf:ó™ÐÔ¤ýî2¦—ÿÑ̦6·ÉMÓi� C-»IÎršóœèL§:×ÉÎvºó�ðŒ§<çIÏzÚóžøÌ§>÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡�¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0�©LgJÓšÚô¦8Í©NwÊÓžúô§pË5¸š”P+ÉœÈ+2`™JôkG=àr²l@ÇrõÔ« S�È[˜•Þ}¢P±›c4þØ;³z„†óê,ä!�#ö°¡<Úƒ¨†¬8‡,ˆ§£8àdÜ!AÉH‚ñ­Ìým[žF´“À5õ'?I®•‡ÃèÑÂØ@¤BDû_N„9c.­œ1qfRü€Jq')âL¡D€•#î!‚Ìbì%9ÐXN¦þ¼X;pÌÙîÖÑ›Q· Üz�¸,ÙFkR3@DʯÏ&c¸žgsò¨˜£F7ÿ.EPé˜Iþ%pVðµ}¡Ø�=„tR±V/‰ºf+h“ÑŽsäP€d6QÇ"Úe!_øÜ@¾pÂ8Ç)1ŒÃ\ıÉ$!½ü2Œšr‘c"F�/%ÔmGÄ0Ξñi¿àJÓÐá´öuù4”k‰ˆœ¶!ˆ^â<”�3] 7ú_è��;H¸&;ïOÁ¸­»©Ñå�oRFX[‹CëR¯›Ó[ !‰ Ib(’Æ­½5ù‚/pFÔtT–ºh Ià‰€-ÉÔóÛ^.³Š�vŒÓ«îCÔ›YNýã5¾Y�Á¥ëÊ«D³cUâü÷ WÁ7�–Ó\¼ÍÌ]ÞËåÿ¼ð\^c Á0€ Èþ´¥\½î´ÑÉ œ–JÏÎŽ6çCzÌ…¶r¾¿�xZš£ë F5þìq®h/¬á,È@”þ Äá¾7~ðÅkd`'@v£y̶è:ã!pG;—¢YËÂõWÑS æGűlŒ‘D*6x¹ÄTS³o™Â8F''ç zØ“D…Åp¼r.«:°, ¨ Ás,Èb~ÈBŽ ^Sƒ5è:!w @T`6:ñ_Ãæ§­l ¦'0÷k$TCVÑATO'}k¶f§–|h6s3…&'"ƒ³CÈC¸GÉ‘ðu2'x0�%CÁGÐÿlÞƒãQ'fU5–—Ó ÁSua‘‡™@‡�±A‹¶^ˆIm �”7u"•´À!ÅY(q~Q#ç|t[x‡éµ^czc èµY÷†s�euè¹ÂTÅVf†ú2dž ÉÖ �vˆ °¥|ç¥_³È#Ì� §z³…0�8™¸ªÇ"SE 'Ä$hV‡½q‡XcU¾ðŒScVìzÄqÐbryÒ)™°�ÂÒ+ô¶bƒØX_ñk�dGLb$hµpävC'‚ÝC‚¶X[”€6ƒEG“Ð5_4ÆŠà=×kí•z”Pðu ÆŠçµlüÀzbv„?Áÿ� ‹tuEf£‹'b_ÚEvqV‡@t’‡`x ñ,Ôv /¹ ²^ÿ�i©‘Ç �'G#Vµoo2#ƒUo Ùy#ÈH‰l¨zx[‚˜ŽÄe̤&Œ!gc'VE4—ã"“DäH(™2,ùà•øŒØ#Xf) òP„˜q ‡:—‚�¨çƒúòŒÀµlѰ‹Ír„P|k&{é!ú“„[au{.W4jAM˜ =ÁrQ…˜”°—6+N….ÚÃl‚™+ø·“64‘Îa�ú($›cVX‰"Ó mùR㊗Y�•Àp�¤]R)FK™7Ž�%Q%u’ÿi~Ã#ÐpF-Ò� _–玛eC@Ç=c€ŸÅ9ÄPYµÙyòQW‰°%Œv�9H5i˜•‘â™H‰ Îç' „ªGV*I™+Ðf…f¶F–itúŊ˘r¶spUÉÆ“Ž´%=y4w5#‘®‰6*�Ä #Àµƒ÷² ‰ð@™›_Tk4ªLÄ+°F(G1w�H¾€qÑ‘xóv)”(ŠˆDéÒÙ�…�8Çô~ŽÐ™h0�–Ý™�vY%xitËvL—ñ„‰lxùB™S˜q2rŸ²�d%Igè�™’dErûpTtPl‘kgÙ`ø&“™”ò9§ÿ�ÓH ¦ 8"[Ò�má>ðó&°ƒqú@È�H ¶&vÊŠUf©¡t4a� ò>�q‚�‰wñBˆ�LŠÇ�2EO—y?j („Š€C~§i”�BŒw µÅY(ÑcMôD`êKóà¨U´‰VV—ÌV¤çñ_ø�&9i+Ä)�äE#VéÉ)Ù°ž„ÚöcUï©�)$n(ŸÒ™–xÒ(.’dtEšbÑ«tt.À—˜‰“ÿiǺmê“ïQ�zü‘^þ{Šð©öðEã�ÄDšºGŨ0E˜kòÊ|$q¢DoQi¨á#nnq_ «F…"4 Dÿ.‘Á&£Gyæ`à#Y3ƒùó°”t Ò©ª‘S †„«QH��HAj«S2”!›bH2«„„‘‘Âa6ÛÂ1‹˜aM†gõF)Ä#aö“‘Ì€HÂQc6;†a ý ;ùz3r‰.t"6ë”Á"FÛgz™?0[±=Á`µ;;6Öd'‰`K8ÂrFô#Í0@ÓóhÎÑ?F4Aû96 .}D?4:(ëfó}¡_Š;<2ŠŽ B¡”t¢¤5’+´„EB"JšpJ¦ÀIšg ­ÄWJ£®ôfè`~ ÖJy æº ¨Jš {á)™=“ÿºÆÄ»«J¸ë1µë ñ)b=Óºœ`0w|°K¤ ««yÀ'm¦›¹ëD–˜€¯ÚÛÑúX,ú½4åY‹{“ä‹t@³Þ�¡éëRæ+–èû¾¶åZ�¼³à¾ô»R/0¤Ñ»ûK ­äJÀ[ w°ÑÀ ¼À ÜÀüÀÁ<Á\Á|ÁœÁ¼ÁÜÁüÁ Â"<Â$\Â&|Â(œÂ*¼Â,ÜÂ.üÂ0Ã2<Ã4\Ã6|Ã8œÃ:¼Ã<ÜÃ>üÃ@ÄB<ÄD\ÄF|ÄHœÄJ|Ã1€¿KüÄ¥`o{®¥4]ÅX¬.Rì½u¶^¹‚¹Y<Æ¡’Àä´æÿ• KÆlLh\jÜÆrü%#ÙÅkAm à5og,ñM5Y0(‡M.ù„:¡{4—Z"g‘у¾�¥ør{uxÇV÷uÓÿê#Ú…uœ¬us<Êå@}ë‹H} )ÆEDܵ«ñt`ÐâÇ�HAÅòÇ�8g "I(ġȼ—Y|V\F¶S´‘1ø'ý›‘— Æ^(~çB£§ÕI\¥1Ä]àê®*KÊÞ, ïÙŒ:¬X ƒÃT„s_èÌŒd»“žÀØ�/{‘_*"öº£ÿ¥Æ%sN1¥¾†’TÀß\ЫÎÐpjÔ îW›T%›b·Y°ÿJoü 6$ àÒ ¾L£‚ÇùPÈx™|Õ=öeÐ&m á\Mš:Ñ‘/yŸ½p¨TÑžPt1šm6o|‹'Ɍǀ$gÚÕU¾46nyÒH� )=.8í!.�/NЦé5Óð¬'èÚ“¯ø°åÑWOǘqÆ"öcÔI]Ö®ÎW� m–ûIž®·pãDÓ�0¥Ñ±Ð"RWÇd \� Áe¥KÒÉkÖ‚í ÍÔv�6ƒCŒaš¤::­ÍÒ͘0 íH};jr{;&¥×W1ÂÓƒÚ¡€Öã2� -Å'{´ì�íÞ:Õ˜a„‚{s�Ц-"^˜rýk´ÏÜÿÛÝFEcv[2î·îZÕ¢�ÜQ‹6Ûül°XRt)úÖ;çØ”c:Kµ"µ6[W°'s Ùµ‚d5�% 1g6Fe\ýµRK „v¶=†³bÁ jëBYAr˜†bÉÝßÞ€›œ »§û»°Ð¬°J `ìëß > nî \Úàn Þ ^áþ ” §…ÜIÁeÖpÅâp´W×á*Î ®Ôâ³1À.> KÀ+^ã6~ã8žã:¾ã<Þã>þã@äB>äD^äF~äHžäJŽQM¼äN ¦ÅZD‘âåÀeµ¸ ²EuÝ„óڗбOÃw�+` ÿ_þe­¾’I›¢À�ݽ®T¬£°F›ð^d~G�5v>æ- ày‰r-¥Ó¦´çš ÆmŽ#n»Øéçþ_J õœ � ŠŽ DÝ­œæ�®ÀNçAá€a Ÿ|/¡¼Jþe@—~~ssìÍ[½eZ4! ;˜_ a6ubFeǘg¡As³ëoõ�ÑaU@›êï»sð•]Úpf]•ÍÅ |õ7[€ŽŸaU_M'äJî7Ü¢±Ì¢ÄTóÛ,Låóg¤¢a^FÛI!Þ?ñtÄ.Ô¦Ì2×Iîw“™ž¯tàJ¢a†Ð~ÿ{ æ VgÖ¿¿º�Œ¬íß»mx)üÇÏÝF¤ÓêÔþ¯$lIuÏé „ºÙZê-G……Ù‡–M8h^„öc"o è Ä76‚ t�×矾ÔXš|=ARÒÀ%é]èEûé«„ãÝkÜžõàƒÍ:Єr†~×ÔÆ4ËæáÙþÙ蓽�ˆ~ô†šô†‚(Måfé—åèáE&Ô$ ¥}"U ´J¥^) [_ŸÕ2­àz�„"ã Ðç1pTk_ îö ÷ƒoÓü<6ÙêQ’`�t"¢ÓU ’㺣GÈIÄ×Ú7ŸŸ#–µðu­Õ�O‹iT�Bÿ5m�ùRÉÒ!††L/ÒãiräÙ™›õfJ•9WOø+ɬ�½ÎtûV_ñzM:sÔüX#›j_¨þàûû«ù,ÂùÖà#ïSò’~_,ÝÒšP7Ï…pˬD•ú„ƒfë~9�ðªîÿ9þ >‚ƒ1ƒ‡>``ˆ>ˆpŒ’ƒ?’/�‡wq“p“ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼·‹‚_ƒqw‚…q/`q/“ÀŒttˆ_ˆ_o‡Æ‚qÂ/Ù’?ppáˆЇ_Ÿ’Ãëê‚o_ˆ/œeÀ'Žœ9túÇ¥bÚê záPÒ8ÿ Òw½BŠI²¤É“(Sª\ɲ¥K[`ÚeúòXœÃb3xpHÈ#7˜| ñˆÆ68]:$ߨ³µ¡ {£…  ÿ�é »yó ÎØX|B�œ‚íß lÏ@bøñ> u¿žß;Aý ð§Ý€NÅz É!yÝ)¨ày �!b((ÀGA^(P…õE¤ ~ò×È~ÿ‘Xà‰(.Å�y†(ô!ƒ-þÇÈÝ9�X…ò Ä¡Ž tÑ‹ýH�‰C ™â‘H u í)°B÷ÁˆÐ’ŒPè^AŒ�q�>>tUÉ$dHHЈF&©æš8­èCy]eLåÅxPy!X™#B8^¢B²)è 0-ä–éC#(„y�(àç¤(‡B�aˆ€“ž@F¦†iª Az¨•2ÿ„—œ+s�æwȧ!œÀ*$†|JÆ!¬R—«@3~zÆ!œ&4«²¾ Äh˜‘Îê^µëAr¬ô ÄH°µ2;% ߉)¥Ùé!Ý)#ŒJz�¶Žjï½¹éC…N4ç¾=&„¦tÝÙÀ„ ‚ìƒç‘a©µw¡‚ˆÄîž ™Š°vÄîÆó(# 7æÇ*LÐŽ6„‡°™¡œ2· Tòy[ö¹aÀ-oB±>èŸdll!€õŽ!¾L7Í�¾>�±Í îç™ÝM ‚‚‚!3¹$^Rž6ñA¦^hêÔ)¨2 a“)Ø5}¶^ÿ]7§ü*8ì g¸ÜˆÆ¸kˆËÓ¸­(˜Ý ¨j7Ø·!g‹¶@_~Ú!�1ÞŽV?~Bä{StÕC“èi™Ÿ—Ùby„¦êNçî´¡ÔQlпÝ"„¦Ö,¤qwÓ\µ@k?¼4Òñ ´AaÏÍcÀ•sìqwîÜ]½Z”÷`Ò!Éow!oè`±E+�þ¾ †×ý|Øsy=ú‘ì§s¶ž¦¹×kçj¦»æjS@ÒE:Ûu‡`TÚæ R´ù5� KÂOÑÒ¦´<-„Fý:ÈŠìÄ/ya°]×{ŸŠg‘Ð<&tN�V†Ây™g~ÃXÏÃÞ--n¬3Q ÿŠÔ,¹¤;=4 ÙÄ»�H�Uÿj A†W¤D‰iâ›´G-Ìh†ÞkšÊ•%CZHxj‡�öð  hÄA~±ñŠ!ôAx8h1îaèJ4ô‘Â/Q ÍDhâÔ8µ‹-ñ‘öBà¤|DII± PCSÒÒU0* U{sÂøÀ*¤||Œ 2F@uRy1{`»Ç/9ä`1¢ ^2x ´å@V±þ ‘H‹Bf2('.Bò™j’¤¨TIû!j‹„à.e¨2ž Bràƒè ò¶.bq!å»#CšÄäí n9äÓ*�Yª êL!4òå*é'ÿÈú%•Õ\æ˜�™Í-ÞqIÓƒ¦B¤Å�”Ob`§(K¤ “"R�„óœ2~o ŽdÈ®«9/F—Ù”VY¡T$>aÙ6A•’G.Ýž�Œi¤E´I üáB‡Z iNò‡Qô'&+šHþ�›ÞtfCÊÃÁƒY/!ESaBBªˆÑ“†ð¼g†"Ó˜*ŽXœøÕYª1ˆFjª6•°"ᎨxåMC½õQEíõzÄ{*åæI•!KºŽôbªœ:«¢ ©aJß¹R ¦´'dRbÖš d@©¢cþO®¡Q‡ò™×ÖbǨy¨þEÅ>ÿZv°ÌQæ¹ »Å3&H{xrHb!òQ�v²ñ¼bŽš´É‚1£LRˆüxÓê*/‰@œÛ!{JPÝ.s=ݱ¢kÇ«›¿‘ ÿZáòšy[�:u›»íæ@Fº�·Ýî!½Ä'8(¾„n±Ÿ&M®AîÙ$x0MRfûô©ÃúοåKOú?Œn±Wû$¯†i[NÂ� Äã'q{QïªW@ô•.÷jÚ~iUc’B“:ãËž•²Ê½ßŒ½g=49`zMb±Záã`ó8¶IÛ.…ib~árÃPæðf Âõ®ÈœºXE1ªKÂÊ×°¾]HûÖËXîí²íó �{T¯FœMÿÍCÎqYêžðŒì?ísi�å‰Ì¥õ³VÙÁøP/2Q÷­ø‘+jÝ‹�ó3Ê�>M‡ßJf”ñÁ_ÖŠ4½.çö˽•Hà&‚²ý: lî{éy.Ͱô!÷žXšg� ñ 9`Õ;N`�È€êÈ¥ÖqV+$P-N\ÿ®K.‘`iü¹H;»5æ%ÈŠbÈT›y`œvª§ ê/Ä�AóË„ ‰ÜÌ›|u†mgCœ»¹Ò17„ð�²‚õ�ž”»!ćn7°Luþ^ùì‚H['€—Nku¡à®×|—7"EFHü!gģނ„oÜ#¬5¸ÈGž�àN¹bÿá¶xIÎr’¯âLAyX¸Úòš¼|3}ŠÌÁbJ›û<Ò܆wSvî•áþüèF¦­ÂˆL¥laFºÔ§Nõª[ýêXϺַÎõ®{ýë`»ØÇNö²›ýìhO»Ú×Îö¶»ýíp�»ÜçN÷ºÛýîxÏ»Þ÷Î÷¾ûýàOøÂþðˆO¼âÏøÆ;þñ��¼ä'OùÊ[þò˜Ï¼æ7ÏùÎ{þó ½èGOúÒ›þô¨O½êWÏúÖ»þõ°�½ìgOûÚÛþö¸Ï½îwÏûÞûþ÷À¾ð‡OüâÿøÈO¾ò—Ïüæ;ÿùÐ�¾ô§Oýê[ÿúØÏ¾ö·Ïýî{ÿûà¿ÿøÇOþò›ÿüèO¿ú×Ïþö»ÿýð�¿üçOÿúÛÿþøÏ¿þ÷ÏÿþûÿÿÐw�à8ðqOÑQ$ 0qQA)pZ�)P-�8ŽP$P˜€P¦0)0$p$ (˜8€�&á�Ž€WƒÑ_ Ž@�[¡ƒ W ƒ0„ €ZðWpFØh��1‚`‘8ˆ8PA)à>À‚ %X€†eá8Ž@aQhˆXØ…�a‡Q„‘Oxƒ4(W�-4CH-‡ù�…בø…¿ÿ�…‰pñ‘H€-@*`€�…$@�ˆ�1�*Ð�YŠŒX*àŽ�*8��ȇ�ˆW0Šb¸‡s8)0P¥è~ˆ†�8ˆE�È)=ŠŒ�ˆhx(‹-À�À8‰—hˆ«…Ò!Œ-@�>@‰W Í†jXć˜Ø‚�…ÁqŠ0Pމpº‰’H‡…‘€0�ô�Œ ¡ïH[H7h�І‰€)°†> ƒ-P€ò˜…=x†žˆ ¹‹QŒxè�4 òH…¶h‹ÿ¡- €WÐ��@ƒ=¨Ëèÿ˜Ÿ(‘(™‘Mh�Á‡vÿX’‘héÿ¨Š3¸…éŠ0 Šò¸� Ù† 0ð‚¡ex˜�€*`…åXè0`Г7™’FiHY@Wp/‡- þ˜’X“帖ˆPŠXø‚Ÿˆ�ðsx‹Ñ€'i“>—8=8°€]�Ί٘šx* ‘Ñ€@ŽA|é-Ù�Y˜¿‚•!˜ Q’q˜9ŽÀŽOH†ù˜!ˆ¡¬â˜¬¢Ixù$€�a¸‹H‡0yQS8®8!9Ëhš�šfÈ…ª•ç"š}‰$p™f¸‰Àé”óBšq�ªÿI›1œq�¡™±™š•í)�“’›á›"(ð ˆ*°‡gˆ�à�ƒyµH€ä›ûž”‰àI€„†ˆº�è)XÙ†çrŸ.™Ÿ�nÙEÚ  Z��¡ªx†Ž@¢A¡¡ Úš‘:zx“,ê#YŸ©œQœ1‹Òň(Ø-@˜òx0É—j¨*ÐŽ1Y�åÉž1„ á‘1ɉû˜Z5š¢›é¨SI�:šN8¥Œ˜…X n(0€ƒH¦AJ€ ˆ­)ž jÍx`–ìø¡ §ç’¦¡�Q:=têh)•É€zQ‹ÿJ‚‘¥›(–ZP•žˆL¹Ž�Ê¥*¡dŠ…ø˜ !¨�r—�š*‡zšÓC�ꄫȃoêªФJ¦Z¨78Žè)Ù©ˆ ¡`™™ÚY§xሾ Çê%ð¢ºê_7Ú¬q—'™©Á‡ÌX»I`KÌIê¹ð¢ßŠ YŸªñ¬f(“ÔŠ¦ßY,qŠ¢ a® ¥ ¤‰®¦Âº�ÄzV¨® Á¯4z¥éª§JËژ𹚉ZžÌú«/Z¨«™é›Ž@��:ã*¡ až ±�a±ºq¸Ù°ú£êŠ€ˆ¸ù(b'ª†:!ôÿ ²ózŸ� ú¯ƒá®ò*-Ù†«šÔš¡Œù§;‹*®�ˆ*:ú ­ »¥ ´Í™š)p‚Í �q‰ÊHŒ¥ˆ„©É�,ê§�µqüª†û8„-®Žµ •¬4ú„®8 á�åx� zØè¶k³X™­ùq™]ôªa¸�µ�@�W`µ>K¹©}Z�ö˜«°Š¹ö¸‰Ž™*˜Œ5J�†è¤ ñ¸œÛ¹7I�ûùHȹԉŸ‹§\i�q艀€Š$�œú �½ ‡‘±X�’ü˜ºG+] »ž ¼g™š5ˆè¤�º~K^¹µw»»X»ÿ º ¯ŸÛ»$@Ž%:�×Q¢e(�kH¼ˆ‚Y(‚ó•)0�6y¿H€�;¹mÑ›†:¬"QƒÓ#ÀP!¢8à€"¹þ¥8‘ QƒŽ@À!1·«˜¢ŒüqUY€ìÀ�Áÿk5Âìh(¹3!°´áÂ)œwîÊ3çƛ§QK�À�:¼Á6¬vª8ĺ1ĪÄHœÄJ¼ÄLÜÄNüÄPÅR<ÅT\ÅV|ÅXÌwk YLrA�z@‚�y° 1Æeœ:@Æ�ð€n¡A°k@Æ‚Ð:0Ç]œk0~œ¡0y0: ?@Zÿ ypÇóÊÈŽŒŠzÜ?Ç‘ÇV±{Àа€‚ÐÆ!¤¼Ç¡°rÈ?ÀÅ4‘ÈU¡‘œz k�˜Ü[lŒ\k€¸ÌAʡˑ€ÊÑÇ¡(5¡²L·Ì´¼°Ë$ñËT±…ÜÅ,ÈœZ€µÌÌá̡РZ�z�{pΑÇ`ŒÂ1ÍÏyf¬ËµüÎñ|ÍaÏò¬ŠPÂqÍüÃL·lÏzpÊ!pÇüŒÏ±ºlƺ<Ì-Š {`Æ� ϰlÑplÐþ¼ÎAÐÑ!1ÿmÌ‘sœÑ@£ºŒ}�° øÉ½oÜÓË¡Ï/m�ÏN�ýκÌÅyLÔ±üÌÕ‘Í^ÝÏÁ�y ÍMmÎÔí`Ó>ÀÓMÑ>`Ñd¬Ôh-:ðÕy=_ŒÎ]½Ìë¬Ï±$�ÒÀÔ}ÉŠ@Ó«¬Ï†=Ï<-Š€=ªŒØk0Æ‹ÝÕ?àÐ��j]Ñ£<Ê� ¨-KË`LÒ � ÑÊœ� Æ!&­=ÉÀÔ<=‘ ÒʺíÇ‚Û{@Ë]× ÌŽ]ÙÆ¬{pÉíÜx× �ÓýŒÐAÿ ÅÂÒýœBkÀÓ? Ø>pÔ—¬ÔŒ-‚ݱ�ÖñíÓ�o|ݺüçýƺ=Û“2 ìÝ�*Ê<ÔŸ Õc\ÉxPÐl¬�0”lÓŠ@áæ=ÆîÜwÝÓÚ­nÛ“\ੌïÍ̆­:°[¼„­Z`ÙaÖýÛ5®Ïy@Ñìüàõ,ãñÔ±h=?ÝMÒ�}ä$‚lÓZ�6�«L>PÚ± îÕLáÜØ°LÆñ©òSÞãµ\å�Óq嵬Ú½æ[^Ï<>)]ÜÌÇü½ÎÂÝã××?ÆAágî'ÑÿíÍ�Ùé¬ÞAîèÖ‘.qNåï�snÍô<)x@×(Ž0È¡ê�é‘ÝÈ�Ó<ÆúlêƒþÓ4¾Ñ ­ñl.Ñ缞¾Ý^ׂéçüå>ÔáëaãñžîêvþÍË~è Áæ�ìaíñEþçq-í¡ê]ÝÎ>ӡ胞‰:Î�ÉÌÎÎá>ìš é“ò•>¨>ã}½ì^¡ Ø¡ÎÎñâ!ÏþÖf½Ëd®ëÓÜïz]ç>ÐϯËÙ|ÖAþ`œÞî,ÞñÏâLçpÝ]žì¿^í[®ÛQòaìÚ®ìÁì>ÿ°ç¡HØU®íV¾å� ïa�é¡ÊC�Ø;MïþŽÌžï î1ò ñÉRÞÏ’îó¼ì6Kî!à‘ÍçqÜÀÛ….²®kàÍõû.ÛßèMMð!ñx Ðc^Ð Ÿ×:€ãB÷™­ëA}ömž€ø‚ßÚ¿Žô\Þë'¯8/ì+_ìÆ•<õñÙÍ~è„}ç1Oí(ßàÁ¾Ï>_Ïvýçœç�bõAäaøvÎó1¿ñ�”®+®ÛÚ,ù>ÞzmúøÎúÆN  Ñ`Þ‡,jîÖûþúo,ûk°óiüY,ðç¢ûq­Ú·ÞûFâñ­ÙJìÿoýOi¼ÝQ?íÄ䉟‹¯ò]ä.ù§,óönÐN希ímŽÖö_‹ïºxtø èCQž‚  ¨ÐÇ-ƒ`p¸G�CÖ`ô¡¢B  êT�!A8îA¨0ÐŒy2 tQaE Erôذ †™ñåXÔèQ¤I•.eÚÔéS¨Q¥N¥ZÕêU¬Yµnu¸f€B�µàÁóêš“F1À¼ù#až@ Sº-ˆÖ'O³ µäzTÏ�»>ö&Ô“3ᢠµ`˜`† ÙY² ™4#ÜcÙGÚ‡6^`X/ÂŽëÉÑ!d—”fØ©°³ÿÃÒ ½ Â6aÊ)GôƒoB�'+Ô¢§öm‚\§ä˜�Cá  -ˆlî„!� ò=²vÁíªû®gßÞý{øñåϧ_ãn¹*%“õüCrôòèO€�´Ø#ºòª3E0(-A‚þÎ ëâO–† 0Âúóa�¸€¬ò4“L6€«kˆ d³=íE~B‘ÐФތʃ¬g£‘ —ª;’°Ôô‚Ð-~˜P�Ä´ ð7Ð|„@E B€š0°+мŠñ0§Ó;­Y£ïïyÖ�G¡F}ïѧ½|øã—~úë·ÿ~üó×þû÷ÿP€$` x@&P� d`ø@FP‚¤`-xA fPƒä`=øÿA†P„#$a MxB¦P…+da ]øBÆP†3¤a mxCæP‡;äa}øC QˆC$b�xD$&Q‰Kdb�øD(FQŠS¤b­xE,fQ‹[äb½øE0†QŒc$cÍxF4¦Q�kdcÝøF8ÆQŽs¤cíxG<æQ�{äcýøG@R�ƒ$d! yHD&R‘‹dd#ùHHFR’“¤d%-yILfR“›äd'=ùIP†R”£$e)MyJT¦R•«¬Ê áWvO äce-GI‚ˆ\€–¾#A ¥…�>K Z@•D� R�D J�‚$‚W(A0¥iËJÿjáPÚ¦SHp „S)pâq…ö´`—yD †Ù3H$â˜SѺù”d^Áޏ:·2OïiaŸ*¸¦öQPlÚð Ç„@™¢@@b–�¸Âú³r¶ÇH!Á+}¦Nå *€Ê#Hà,3~%Ȩöñ΄Ú08ðKŸ 8-•Ÿ#Nz@ÍëdJ BÚ”hº'ý^P¹GžÆt†NuªSêy&ä Kõè I>y’@<�g""’ˆb’*e]*F‘t.T¬e aÍ¥¶U!�8)TÐK°fJheªZM³º­½¤)FšI\sÿ£ ÁA zYX‚há®ÆŒHG›¢ÙD\ y-ìA‹Y‚Æ:ä±%P'O—*ZÒµ š-f è:Û¼$ž)�mRò-xµ°•½,eÛªÙ`ªà¶{ý)Fh Y̧m­C[‚ÄÄ®Ž˜n ^IMu2g±¶¯4aYµâ6€Èk4£*Õ®ó¥¯Þ{w:$¥ �A"ìëÓDLW¥q„Z°Ï+X  ÝidqOö>å wõ¥u«9ZG@˜¨Ù*lóÚ[¤`´88èP²Ô ^!²-¨Šùàó+.1L ’ $bŸÅôiBø:ÖG¤T}sÄtÌ`HL–)ÿfb¸:°1AZpW–øž>@1iuŒÕÈîÓ¿U¦í2-‹D´)(†¥ƒ�\“¶A.Á–z�"y½©¥l…ÅbÌ+ DWï_ט4äI¯ïW_üí²õ:ß_èL X é¦S\%ÂD�'Hå}@Ð×w>éNѾcãO}÷‹®‚�»ÙŠ:‚0¹Ÿ‹¬ë[8å“(î²·„Ø<ÝR²­Õ ©ý›¼áÃæser%�J0à )û´Â"�äˆÏ½åJ»©°?±ë;ªÔS ôH ¶K 3-€„ZC¨º+Š\@‚è/îk)%[A/»-ÉCÀ‚p>óS¡±ŠAúCŠâKˆàSº³‹5Žp7’KˆZ‹Š÷›»‹º¼§?Žp=«#;!ļÿ•+,�{|¼µ¾�ÂÌúÃW³Š?´C¤x^³2Ìb<­Š¿šr-$ˆ ,Š+T2À껂Ø<¡‹Âš,ADŠ²Û¶Ì+ Ø+´�‚(OtB§¸Â�“­1$E+$6Þ :ô1ìD0k>ëúB´â(bƒ¨\T Ô¼^ì:ò©A„D²KÁ£˜Å¦8ÆÃ+Æ'³XK¦uÓ¿¨ÒC0,F‰Äšs�´[C €FŽx:0Œ?iÅÒ¾Ol u+²Ä@gÔ.@HA¤ã6¸2›® t  H4\Š| :¦rEp¹¼3ŠE›1�‚¨ªÉÁ7+´ýƒÿÈ⩊¼1Âû¶ªñ)Ó[³è’Ȃ૥Û5„&¦z„ÍÓ'þrÅËë­‚„ŠvT0ÅÐÆ¶ ­™:(zã…6žRpÒ¼”,¦ªyÈG¤+%3Æ,B› :ž ;Ydª»É tG ZµI, ùª/›²Aúª/°Ê½ÐûR_ó*cªB£H²°¬¯°£/¹ùº¯h$KP¿–¶ùj,ÿš¯kJ¹·„b6±ÊËôš;Â\¿ŽJ@ð¹ÀÆ:;¦ùò3¾ÂƲ´¨¾Y¯^R?„9,Š}L\¯jb¶ÏT²vª3ËKÌrÌÖä·ÍÄKf;*¾|Ë­ü¹Á̽?ÃL ÐÌ®{%ÿ û2]ÜMuK¹^º<Mž*2ÑJåŒ7Ç|¦¼üJùê¦õª08§#KÆL­¿«ÌNLŸÉTÎÜÄJ ò¬ò;™4œà"1÷A»¤(¶Ô-À¢òAúD»÷l´KÅØÏ-$°+x0isÉCPÔ»ŠzrTã' Uî"Ðq#±«ä ÝPõ¼ ŒÂÐDÅ%0k,Qu ¾JQ®LO}Qæ(³T¦ÅÊi³€H;QÝÑ Û§}âQ¿Û'åQ"-R#=R$MR%]R&mR'}R(�R)�R*­R+½R,ÍR-ÝR.ý™5xŸ. S®aA(½)ˆ3�ÿýÁ ¸XŠ+ñÛ9Šƒ¨�ÞYSê±ë±Óõ( @ìp(õ _ Š^‘DÑ×�…Á £X˜Xú¡AÅH‘ pIž=ЇPïh ©QN½ŒÈISDQ„9aGð&(Œø+UÑ ÇÙQ”E%’@ŠÂàÚÈ0å†ñÇ1Õðø¤�DY£ØUŸa ö €E(W} ɉRü �E¹ÕÑÉU¿�Tú��÷ÖÞa ¥pÖ¦@VDQÖ¢pTŸŠõ@„Að�B çj+½V”ÐÖ©8níŠåÉ…yæ8•qa ‘S!žø×=à‘!½Š\%X7) Xÿ=@¥„ƒmâ Øº  ÃÐØ¤á��È}i�¥„A• ©X‘UÓ�0V£  SéXòñ’˜- ò�  ôX ˜ˆ�øY¿Áâ)XìøW…Y�ý‰�pq…Xƒ Ø9i�ßÀ«E•YZž[É,{¬mØ–Ñ�é•‹•Œåù“¢½«ÅÚ9yX¤€Y­‹5±gÛ(ÓŒ�Ý¥Øfÿèø 5áˆäEh¦¡Y…&rf w�e]YŽ›iæˆ�ææÊéâ�®ä,Ñ€. �hžVK Û&Å׎Ð×§à�¢¥��ØA¾�Ùæ|% ÄÀÛ¶ ãÀ—<¾ŒÈ�j©~êT-çá`Öîàè¢hâ`wn\E.éqŒ(QcÓ€dó=Ø!hž…æõšNŇp�@ÞF¬YNAa®‘Î,–)ˆMak‡¨j5=Tæ]cXþe¬Tj‘îVÛø¹‰ ¢Å[TÜCÅAk‚0ìÇhç‘à™j©ö  €„ˆfܨT&ÕiêÐ }Qê�ðŒ° ‹ÇÜv|¡íÿ­ ž—pl§Èâ.nãÖÚ¥ðf…6�…~çá^’ÊþliÜéŽ|dÕx~e¥@k®þ ×Fæå¡í�ëüàsöv= 1�A`@›¾î×véˆÀv—2 éÉfÈ=Ÿ™ÙbœŽ�Q„ø>S­ž ZÆ¬æ Œ�î›èm¥6�” ‰È•uÅା/�‘áHÝã6nÓ?øÿƒ>@&àˆK¥R�.ëNý]¨ÀðWuŒâqNw®Âïõèë¿þìz>ðG†nLYð³ èî(’¡l¹Î�ïNëØ¨Ž°%Ú½ KžqK-‡> ˆn¿©áyŽ4A‰ûNåÄåÔÿ: ¹^ï07Š> �[éA8aUiÛhpˆþc¤8桦 NÍÅ V§Ð‚G(ôBg&Ø¥“fñb‰mÅ ‹„F Y…› i‹·ãdéb¸Ñi5Wg0ïôʸsçb Õ¨î¸ñêþЂ47u¿&/ C¶k„àîË@ Zòî<÷l|Æ u‰Þ¸”%�)ñdÜ8÷�¾®èúÞ\�Iðì¨äò-iÀðñ(S‡HGp;ÇqÕ½‹Œ'á06ïp'�çõ1ÿqGGw»þ•¬¥‰:ƒviÅØš÷@‰woUŠ0Ö�>˜†?lµø7åãð€—øZIŒH‘�™Šu×òH_ÒFy”HQ–JAóÕVábîPù Šx–OÚ�hM!X‘ }¡” ðõÈ•®1ŽCípGÁ›–�œÓ¹[Ü`—†h–g‰–i©–k!‹–@‘zoé2UÚfÿtÁÝÆw–†xN–‡™—­?Œ| ÇÎ妎�1Áw샱X…±dÂ)|ÿÕOÂqýÒ8‘›!cÆÿ �鈎Áí�˜Máð’iˆY)=P–qâI™†~§µ˜îÈ–©@ ^‘–V¦]ËÏ×lϘz™—\^{ñ¯_ÁZ$&'áçOþŒø}‹ø:g(ø(hÐÇ� 2lèð!Ĉ'R¬hñ"ÆŒ7rìèñcGxÖ€\HI‡: ÒAQˇ&[>\H‘K�JÉq�"�ŠPrŒY1ЉDu>Ô²†¥O†ZxFe¸’æDAz2NÅ RÇH‡Šn>|*HKD³U ^ÍéPäZ�c¡e*ód\&ƒF›· –±^íÌ`”0âÄŠÿ3nìøñâ5x’Bƨh@åÌšž8àïæÐ>‚ÝPOiÓ%SVíúuA�`Ó®mû6n��𸽭C€ ÜÂ5ÿ¨+1ÐÖá�ýèíZKká»�+¯ÞQGtëÚ·sïîÑém=yò ø1Û;z�òàŸÚêøÈàD�%H…j¸!‡zø!ˆ!Š8"‰%šx"Š)ª¸"‹-ºø"Œ1Ê8#�5Úx#Ž9ê¸#�=úø#�A 9$‘Ey$’I*¹$“M:ù$”QJ9%•UZy%–Yj¹%—]zù%˜aŠ9ÿ&™ešy&šiª¹&›mºù&œqÊ9'�uÚy'žyê¹'Ÿ}úù' � :(¡…z(¢‰*º(£�:ú(¤‘J:)¥•Zz)¦™jº)§�zú)¨¡Š:*©¥šz*ª©ªº*«­ºú*¬±Ê:+­µÚz+®¹êº+¯½úú+°Á ;,±Å{,²É*»,³Í:û,´ÑJ;-µÕZ{-¶Ùj»­GW<â��¨…¸Üš{.F$ u�q!’ B�*”¯]‰¤PB ‰8r¤#%ø›Â�öΛȷ¢%¢ €$‚n–Z\àƒdDÂh  )ìË 0‰½L9¬¯#8$R®]Wl\¢# —@ÿb ív¤ $›ŒrfŽÔ\$,lP"SLBÍ23\ä-øpÀi$äBr…Jo·2�‰ü R X+F‚×$jM" L¥€là ‰ÄµÐBÔZ8Ò‚FTÎGçx¶]gôÈà 6”~ëT¶cˆ£XÚ"öË”âª9rCmÄ÷A‰¼ìPäz爸å%ÍÐØ]¡u]LÇÌÐ*�ÐÖ '‚–æ7Ž‘ì‰PÎÈ”ãຠvCäˆîZÈ»¯#÷¢¬õîk½õAW”@B ß^�ÄsgLøD§Óý<ì i‘ˆëÕ?C _¡…q · p ÏÛ�C Co:ê©.ÿüCÄ'ÒøÜð&>{½ gãÓ¸Ò—/ˆ„Ž!&S ÷ Q¿×9‚\¤«æƒ:Í-L‚$¸^Aèg?"�iE‹^ížW:ŒœNPaØÆ—¾Õ-ät a èÕ4Í ucHç â0Ïå( ;bÂ,À½‡¨ y¦Û\A`�%êì)è¡q�ÿMî…!Þà(ØÅûQÄïjH¿ð¥/�‚h,�Hp…+( l-ÐÐJ`5€,} �DÍZ–< „ndgcã!¢5ê©°]4k�·®@=©T­eÒÄíˆ7=’�-ÀÁ#pP‚ÀDŠT¬™(±8âí]Ä ]ÐH6Åæ�ïÿLK�Ê$I‡ Ñ!-¨À˜7$BŽäšâ£·EGLî= PùB9Ò±}ï¥(Ù¥L2sz(t¦ �h‘EVïyy‹%3�èµÓA­Š„ëÊ-z�‡ éèBªO‘@\3ÿňWûéƒG(Ì!5Ú qÐF†¬T#muˆM#šÈ†� `>°Ü# è´$ƒ>øëBpàD¡fŽŒñjSHYQqÖ‡nü§ø4˳ºzðÔßi qú} ‘+DúŠÃ†ô�£ iжVXVt˜½´*Å×ÀŽ × ¢$¬× AÚ�”t–UÕi�$ò´kU‘ܺÊÌÁ �H=dMÓ…ÈV¤@iAÔšÌ~µrnlˆâ™Û˜2­L›HxÛ]^ŠV|-HÁ^÷_R� äíC®°ENjm¸£ûN+?— yŽ(®YØÛ¦œŒÅªCÒÇàD ¸ %µœÿ|—¶`‡Ng¡]Hòtò[÷Ò„þHÈ�Ë�ÇÝ”}?dßucôËŠÁÑøÂliÆ�ŒrÎnD5JbO�<â»I±‹ËR¤ÞX¶.žgèÊû�á‚Ø"¿{:É…ƒŸòŽ•eæ±/ó5bÖªàÀÁ-çF¬µå�¶ nC8ª±¨�ÌY1ƒWf9-àQVû¯ž�HâȪ¶É0%r�Î6•µ\1çhK¸/™F|s¬EPýXÖ2VpÁêr�«Ø‰Z–˜\µn‚÷¨±§Î÷ 1†rw± -OÄÒÄ´òc9MÛ‘;½µ>+C/¢^áN䊙æ² r¯Á9‹Ù^H«¥ÿBÊ3ÙÏÕ¡\7ìcÛvÇÈžrGª¼ÄD‹‘b©kÔ6öZfoƒ/D¢|�üb€í…ß[;û@ÌÍv“kœ0‹ÔÎFcôFŠét)» ùÍbçnÜë»%Rs!öEÔ½ DÛ‡,Ÿ§À3ŽFx.0¦F³`cŸÂ=Ö™×­�õ«e¦r”§k‰{÷0ùM‘æ$oxnú¦CÌð|¯H¾¤¾=¿¨�ñÖûÛ!|9`ôx�ßµÀ–“KŸÂ(¼V«\"3Í:¸›‚=j±§_4ê36ptë<"ì�y 06TÆRMfŸëý’™9d_�#ü†Øë-ÛˆQM_ô»Ã#>vÿÍêÌ ´væ¶Ýt¼ó¬:T/h{/ ‘¢«tL±F¼� \^ÆÑ"k�‘_jk™è5�vª—ˆŸ}wˆ‘h�]ƒ‰I|m4�ù´.ŒiÎÛœÿü‚$µ£MØ~Aˆv5pFìaW©gÚˆ‘\ÑTA Œ|;:&Ìj]M®5­ýöë5cs €ßÝͼÀQ  ]YU‘@ÁU]‘T AM—ó•~í_ØQÆŒRÌŘ#¸Ž˜-ÖþÙ�Ç- €ÕW‚biAü]UÑÐY€3˜â±Ÿ÷}ÕËuÕ¢Q¾hÀÀà Ž_¹$_i`úYø%.™ Æÿß[A *ßzíŸâØ`±/ŸŒüÕnÁFÉØÍLj“DLN=Ç–¡Ê¸‘d%R(… m”ÌjÔñU„J$HVÇ}Q0 ßCä!rÄâ�¡ÒÉÆ$¡j(°´Ý!>âˆÛ±$Vb†hÁ(Å‘‹ëD“Y"(z3�â±<Â(*b(¦¢*®"+¶¢+¾",Æ¢,Î"-Ö¢-Þ".æ¢.î"/ö¢/þ"0£0#1£AÐ…1&£c¬A$Çk`@yÀ{l†xLcC¬GÌ6V„dðŒ@ãH£ÂÂx`È1*„GA8£ˆ(ÂxGhè€5ã €=¢ÿ£Øc6†Àf¼†ì�K`G@n†I¤J0ÇŒ8TXxcFðD† dAîÁ>B„ƒðD Ày\CbDv¤‡"ì:ZE~„l£"ðG]8HsˆNè…BІV,„IŠÆd$DtFŒød_t`…ÜäAä¤D|ÆCSåD<ä€ÁIVÄP‚DÔ$=Ö¤ÀkÔãkd@UOf†WJ„SºÈY&Ì åBˆ¥gü…¥N¤%D@¥€`ÀTRDU~„à< cY²W*F`¤fC æbèˆÇHäC%BbÆ fPGL(‚:FfFdèÿ€c®ã^‚æG:f_úh®AäA°ÏzÐ¥jbÀl”ä:b@»Bè%[ %,ÈAlfgJfAdÀnþfM¨cD&2„ `£o¦æ[bä|äBpæ:ÖæPægbçxDç�jÁxÔi>VE?.)r †„fAŒçètä� †O\Fô„X¶†cÎÇŸþ£lp4…�.�¾¨"ܨXæFZÅ(hJhEBXè0h~†OêA¤&H?új*htçzŒ®$a`­ÄsâÁIæ‡{&$iL¥;vj F¡hˆŠÏr^)@Ú�"à§` €h®Áè�F¨šÄzÈVÚEKÊL¤6ÿE€bÄJ<ˆ‘„XDŸÊèA˜j»´‡qôiFìÁDE»NêX¨AÄ*ÄäjA ¦dÔdŸº92„¹bÄäAÁ¦eb:ÄÈflüèZFD½Z¨B ´K‚’lA�ã\²EDl¬A̬Œ+<çCÜë<�*[”G^H£C@c]´¤D^ìAÐ(‹"Cà%7Ö(a,lqNcÊ„L)DجAH'E¼hCüå{ú¢dìÇ쇰¨zÀlA @ƒ¦(a`ªT‡C †jt#CŒlpj¬9BD‡åT¦(LåGÚìoðiX,ÄnÕò,ÇòmÝîiAmF¨íFd¬q-Ü®¨ë„èÒ.ÛJÄå„Ä fM®(b,çP‚æóú¬ªÆkSB¯õöcƒ¤F8ºª+º~oß.¦SR¦Ã2nœbí�j(Ÿ6lg€ml¼d�®+CÜîšò$×ZÔÆF^ÌnC ì·ÆÅñvmñžnù%Û’/|æcyb/ãÂ¤h/CðopÆ…ØBÄ�räBЭªú¤<æ�,ç‰.Dþ†í|ÚãÈêîF,,«&[¶é[*0jp®»¯ªbd«£ ­òæ§Ôê„k.¨oJ.ÜÚE® ñPVÿ¢²%E°f<1Cñ‘ÊÄ3ìÞ.ÄýR�Fàj%Ôº¯�nAîÄŽ/;†1_8îãb±9n±ø˜D“z,ñB„ú¾eF:%R,�yô„ƒè¨Â–î{+×GChpëB„Žfq³Eg0ÇŸªk²oDüFb¢k«ª°Fðn÷F®ðzdºJ©dp×*) >ÅÕb1ïbŠ’ñu²„*nð«F²Ä'å‚hÁذfH†Ñ¨q#w2Flñ' nû¦«hü²®{è>22E¥4û€Æ[£)«&�¦æðbñŸ±…6ì7¿ï›nt ª/W§T83ãú²Žê@<³ÿ1CBgPmÎ6l ã$ÛîóUœï[E ã)�šiý2„!×°Frð37´ã+å_âõ®F57„Tôhà`nÄ|â‡AD.3†d°- #/äÎ//„1wñ[&óÍ:p·¬HÇ$�ÅOêJ_1ã¶qƒf4Cpòä¶23;ÇV*MD—z®8?ì{þF �q�pFï¥D²,C$èþ6lq,ž"òhÌ£|P¶&îvdU,CGKîEd¬W;²sŒëI~23—²/ó§8¯ò1’k0"s=·5G@cT¸#kôPtG,goB\++aêä=²År’°Æ®Lp4†´ôûÿ8ÿ.3,×£a i—6hŒ†ïh¶C_vä.&è­U'5Äœ„…"rFk3*?êâÆðCµ©þÀè r̶d†OVª[´¦}v0Ç5&ì; Ñ ,ãz£ä#9[çx°»&vC$¶F[„4ò( ;kyž$pï‡dΫÙöÄú®=/@ã¬âAòÊ~[ç¬rõE˜jyf­Öòñ¬6GørôHODí®£|Èíãw�¯ö„‡FyÈhÖŠGò'…Ó¥=ã÷H&6È �rtF²ÇdઆXHwbûilü@p´¸Kxe{F*ÿìhÍr´Ì~{8…·†|ø�#8e7ÿé@Ê}ÿ€¯–‡8‡=—§|Œ­^¨c‡~rÚy�W’ë“çG8ª(…Ï8~»Än`F‹Oø�WqƒgxFÏÇ[À¬8_t©`�±—‹Ñâwj´är–ÇMì÷Òúc¥îÇTf4†ÊÇk‚øA€,8EL‡CD¹¯A‚Út“bä—ËÇw©¯ÊÇ“«xÖºí¥GLÔ²2Ɔ;oÄL´…âASËÅI„ïmìÅ,‡í`d„<7gFì¤E47/åzbد[T@„­S„]7±,?Æ/ˆŽrD[ð’_HÄ\Ï\(2¬ëFNO*YÈD³ßòR»°ãê­ÿuŠÓÆœ»:™±C ´‰@ÿø�˜�Ì*»;°WÇq§zPûõkðp�¸{xßû�@c†X:�è8H °r`¿C€ÐÆœ;™äcǺ§C´æx´G^kˆXŽGÓº–LüuÄÇ|$ôë"P$H¹«(Ë÷ëˆ8È…Š„$ŧ<Îç¼Îï<Ï÷¼Ïÿ<нÐ=ѽÑ=Ò'½Ò/=Ó7½Ó?=ÔG½ÔO=ÕW½Õ_=Ög½Öo=×w½×=؇½Ø�=Ù—½ÙŸ=Ú§½Ú¯=Û·½Û¿=ÜǽÜÏ=Ý×½Ýß=Þç½Þï=ß÷½ßÿ=à¾à>á¾á>â'¾â/>ã7¾ã?>äû ^AËy �E~ÒkS¸‰ÿ‰ÞÙŠ$àc“×õIbQOþßÑEPL€ Š*H1˜¸ô Æ<—§ÙaæÉ#4 W\dÈ %6”c”Ý': ÆHèLòSÌá…†Ê,¿Ë¨" (MõsEü°�ÜÐ Å;â‡SÓ=ÿuD"ø~ <\Ýb×G0¢_ÒÑwÉÍæwDÏà€Ô|¾jx¿F ?ÕD >ú0xa„%%L‘HaD…€$V´xcF� ZPxA GŽ$Š4yeJ•+Y²$É2Ä–c¶tDb&Ƙ !Ä‘"gBŠ -< jrhN†G™6uújT©SMŠTaÍ„‰T�P1ÿ!¤D-,´ˆ3äÁ˜Wj9›GL£>®¤h‘ÖGLHnK”�yRlׂ'µ\˜˜PìZ‹�Z” q·¤·‰�5ykט&Jñ8dL°tõ¬{7¡£ŸSô<ב£­é–úTK �i™‹ÆçÍ 'û`L"fGgÿ2Çì6E‰®.OV5mÉ1µ0“³Æ‚‰úªïc9ëÑÄíNÁv½�—yßrŒë˜ù_¸ºß£Lî½3 š,2ëØÚ¢ö Ê­„õ Ái¡ÈÎ-±DðÚè'üÒ"2±ÈŠé» ­³P-ð"ºJ!è¤£Žª]|Ƨ¢ �m´à<‹ ûÿ3.HÄH®¸à6ƒ–Û-¿­¤;H¬Ï¶¢,6€´M­Ê‚Ä‘OºBÊ+IDK“®P®"�Œ©2%jAƒ+°LÄÃôÌ‚üX4òG¹¢ÎÔ~Œ N�<è 8cËs6„Vr9@"ãr7Õ�Ûì1§Ú»B ±âð Gê´Ï¶Z°Ò‘G®p´ÓQá4/U%ü³Ñ;UP Ûâòȸ±êdîN�.¸©¾¬,!.¯+Ìê¤  ˆ$ê ºÓ/£B-P�9Y™DNOv;É-´¸Š„¹�íH™ÜdÙÊH¸âT¼`há°ƒ\�•X‘�c–,zÏD’¹Ô,x¨3 æChÿG‰Ñ@ÑVƈ%ž˜âŠé»xK�”!./ôá‚øT€X¡‘Ÿ¨RÉ6触°E,hº�ºÇ|uîÙ Z�à°ÂU¹ƒ/¼‚k”ØýÍ8@’V¶X3 JV29Ûëu£±+*Œ3Ž9J˜„ÑPÔû ;d.ݺ€º ö h…–æh¬… ¬¢ÊÃ’S¡¨#² 1Â-�ôÒMGéj ÞΨ"?³ààº#3!¢-RÝ¡øÚ¦î°4h 5õ�ó‚â/¨:!ð¦Ktqr\GÎýËÿöªtwDkÖµMÊ‘JdÊs‡Úºúäï;ãú]ŸÌ¬ës’Y¶ú•p@Þ Ã+ aéÕbm-üN W‰ÝÆDâ9. -g]SH1&ŸÈ0P8€Ä#–C·úi«{'ITX>²ºƒ<‚�\ïè"7ƒ`ît)Tá GÇ!ìÅG9ºÈ»pp«�}&? òBBx¢*d^Ñ ¡Ò–Õ”àÒ!ÁÍ6âµÄk)»Þó6ÜU¤? A›‘Lص¤mÑ"u!�R½›˜ÏK9ŠèÚ7»ž 0‹jéÙ;ò�ü@.ÑÀò('<„˜,UH|L nÈ‹4&{' ß캘°� Ñm>ÿb§vØé©Ñ|PUvƒ±Hþó ŽHé`*"O\ ^†H6¶-ÎŽÌÁ# iYK[J¥1°@ ,°ËJr„�Cª]Ï¢œ_FäŒÐ*ɤÈCnÄ1nâŸøX‘Mæ1"�H 2Ñf4¡\„�YÚä˜ôÄøE¤0_y‡(Ó™¿$ç ªT‰äÏ·M й“#~wBmÑ“˜h‰ÛE¢hû%/2}чp€#Ž’iï 'åH-fâà˜ÁäÔÎÙ@.Ve[™$z2ŠÎ¤ Ð$ìL�â“AŒ„횉"@ Ñ[Þ§9Í�™”l ýŒÀYÆó›Á˜T2À…è.�ˆµ!á±Tœ¬òí%+£VäW%̉0ÝY­*Ïš[ͤNI[ZÓšWPňT�ÊÀ°]“‰cýH¨Ú¸*ðo^kÏI¾ò<&ZT"ú“kù¨»8º¬3‰®DÚzBýH/€ì«N�ŠØDtF&}ÑíªÖ���‡¯`5/k¬òî`ºM`f¢˜!€ÑpJ½„D0„!,Ìo¤ëƒs2.ZŠ•œ…Dÿ‹‘ÛzË!2ü¦ ~ œêå´–ð„7Ù\ŽôÔ¡BC^n¸öW±,DcYâ«…Õ‡3¤àH´ªEÀ Ô創-ÙóµvÖ–D°¨¡ÔñU£ù 2<.�‚µŠ¼†Ï¨¥¼+FàÛö2OÇM«,qÿÈfÂÀ�JÁoU›3¹e¨›bd´©Ÿ¬‡eÙŠX‡¤ŸV† ),ZдÕ“ NÇÄhÚs•DÈ*Ë£]†"…’S)çÉÎ KE5°šC*vHrÞò„)]iÖ¤¼:±ò(â8E§x¼ÃÓ¼ÄÔ°‹—) g†é¹é1°Õ€àœ´L)Êhh—��ÖDf#ÿÑ:_Xuõ«ñ"$Î&Í$Ô%¨-˜*:놳qƒà³° u.1‚Ñì1 "º0BP\RPýq ÷ "H±!û‘»P$ÊPfp>q'Ò"’p!!" @&ø1!#Q!.’ãPHQcò$ B!õàcõ%×2@�Ñnr"z!’P1€‡Q aCP ÂñÀ$1@#/ò#¡Âíñ%#" I±(_rB%—282"Ö`Kr*[ò-aR&©" "(YR'¿²-dró1/ÿM¢ ½RäQ!º2y%®1!´1%'2B!i�)ŸÑ'÷ M2.ÂA�"Â+ó.�‰p"CÂ#K‘*ËRÚ!r 2 R4#â(ÅÑ´x1œÑ'`,+¢ ‡S8á7ÛrA2@aÀ uÓ!|Ó$2`9€9ëò6í4iÐ9IðEp Sr¦2H2"W+2òã3"É’™!Öà‚@ÖàÁQ$1@öÀ$Y²U0?×óôAC. "&÷@´`;½PüÐ8op}À-@;ã’³:ÿ7ó Œ0"á¶3@â±’:­s@úó*?Q6Aâ“As¢5µ³9iP!A‘Q2€7U”P~Ð$÷Ó(—“@g42”9'sGÓ.ƒ  q1”“ëpB4FÔ ?ô(Ëôz°9�8íÔN§Ò7 €44Eð`Hi•–‚‘3 6*ŽQ!(–D‘RÄ6]Y’!ÿ)}€÷à -t`aònÏÕ ÍU$ƒ6#ÿü³dB\S0!æ]cõm} 5BÂES’k9¢“ö  Ò(‡ô çQ1;ö"ŠÖ #Ô6%„²j»–tc5`“r3-6YTg÷a¢Tr÷¶nóÖ9+RS³±O× Òô:@@Š—$sÖoÃus2[Ïõ_Ö(wZbò`dGQ5å¶EeQ òw×k¿m½7#.p Î}Ñ÷zgqò ´6)ÉW#|ׂ`?Áö Nñtév@òöu±[Û"p9B-3×(#âpõ|Ö¦±mSòi‰u=�tF‰l-÷w‡Tð€E§±F“r#50T!ÿ@a`1TtQ‚~�—ÿU$Ý´$i˜ucµy¡7\#Âl8/¸0ù–do·r·‘gÓ7}ñÂaD'q‚ s,“7k‡·017q�Øq÷õq5"à8Õ1LX]B†µÕˆ÷gÃ÷k›5mE‚:3€Žë¸Ž™T"x˜h%Ž_ØF#Ó~ÿ³YµàYù‘%ß6‡ù–ƒ³‹“úšð9óàcøµ"Y‡óׇw—ŠÓÕQ­0>]1!8�‰5 ·_OQf³4[ÅÖnySàP…Y"bÑØtñ/U’x#|Ѳ‚5Q‚ã•�-ÂA70H™V�©r”·‘Cíš½0uÿQù ,—¯Á¸Š/ Sðô²y—V"*±�ï3Œ3<™sWÏøwç–{åW[Ù�S9bv0i õl‰ž1‚~×Òm‰ãYçv�};ÃxÍY[×·" x,Å5i!Ø!ÛØ,¶qË5^ÝW"z¢5x”•°”ïõ_ xS‚ˆ-4[ky\wwgHU"†M—†sÖ]å¹á~¡6vã5‚+£«Ùv©w-Ú"æt{ñ–×§/BŸ ¶‚R"’fãÒs™šëWz›WŒÇpš‚uµ·ˆ‰:""wžku|ïk"£It�ÁUĺlù~‘RºE ºZõ±yÿ¡‚_T@!¹\¹\)·1›•~"m7³{‰pT/$<9¹‚Ç•%í– ¯9"d×(ƒ64±˜ù:«%¢«[rƒ tt¡¸©{¬“’Uö"”ÖF#²]/DLw·{ÕJ-ë¶™+2[øŸ‡4 ]S!ž:³·Ù‡K::·¸³“[ÓV ïúiK[2Û˜�Ç• ÉšhOù¬k©žãXFV;9sM:)Á5µAx†“ó× S4n»;§ûv=ó3†5I÷{d¯úp ;�á5)Ï!N8)™Ò!‰˜±‘Kƒ”ã˜�ºÛ©™¹rEºkU¥KQCÅ¥‰P Ö3[ÿÙ³%³W5é;Œñ œ7b©);]kú~…‰uTÆNgö5´Y#ü “˜ÅU›m²x‡ºƒ›€÷`ðÂÃw)¼šS{H•û¯�Ö“ã²:ººQ²{UsE‰õRy9¨w,v*yq9«Ö¾íV!f4¼k©añ ÃŔλ*ÀMu fA0bÎëüîÜqéÜÝŧ¬F×3@ÁÐ:€ÎK�`×Ò9�1's¤ËÕ¨©2µ³µ:é\ïšÎ‘øNÑQÝ×”$×ñÏ¢Œ1”`g3%±ø³}Ú-¿’u 6µQ½‰ŸóAV>{a[0À9°�µý¶ A³aA�Oÿá6@-4Rõ›¯ð›ÿsx×±x÷3i£ö³E°Ôa_Qb•?�*=?ûÔéœÎ³�q«sMŸ=iÄÔU…½p‰×Ïi½pç²~÷sÎß�Ы–$?;óž0méÜÒéÙ^#ì\ Ä4Øcñ}ÝMÓ=µßvRû%�Î÷Pz=óÝ߽ƫAi°fó`ÝÕÛš*¸åYÔ#KR �P«ãQýg«ó‡7&éœA^+]2›:α¯ Crž�¡¹UB¹/B™ûµ¦™”¡¾-ót «>â™~ …S/âÆAS8¹5ç�#‚�áÞ"Zp²e¤í¥¾"`ú8ÿuÃLÊ5‚@áìqíYBp6'‚ó7µ10ï ?#8Z$f9¦Þ©`ð›žV¯¨;Ýó¬™bªš–ô¼]ô=0¬˜* Qó[ÿbU?!°ûôR´tvœ…¢ýõpQWÙò�B<ßõÃ{Ø‹·>ÁR%½6?OËØz¹ÏñEoù÷¼Q‹ñƒû]$·u¬pû�õó¼f³_%Ä¿÷pû¯ŸüןýÛßýßþã_þçŸþëßþïÿó_ÿ÷Ÿÿûßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?þÞ2òN°þ¨3¹uùÿÿÿþy1ò‚_Fÿæò¨°%àPûP¼1ûùÿÿ_ÿGõùÉðü¥"´!¯ž¹§soË#â¶þÿÿÿëy·KÇ�wñÅEB¶±�³+ºÿÿÿñ¼û¹%BK†=yôò�0!Â5AòäÁ ÅÇ…>êÉd� *8`O Š u4̳G‘"$[º| “âš>̳¥ 9út)ˆ Èœ-×`Èà#(F�%9zô¡ˆãH:8šÌƒTÁ©Q1hä‰ñ)B A:&4Ê1"IAxX¶Ô�GѹtëÚ½‹7¯Þ½|ûúý 8°àÁ„ >Œ8±bº3)ê˜HX‘€=‚Örè6a  2` ÿd„:& Š*€­Y„’)¯”g@æ…?‚(z�'®]A‚õöý[�Ú¹Šq�áSº;{úDJQ‹Ã@Šé m—¡qƒŠ2ü�Kƒ £êуPKrŒÅƒÈ H€\£…<½}B x‚<5ZÛp J‹ˆ`‚ .È`ƒ>a„N˜�l:¬±Æ…Vxûå¡ÐÌ&âh> 0ià¡P‡‰GÑä)$Ÿ]*�æYŽ* ˜Óý}¤Ÿ]dŤGÙw—�FjâG3&$H�>pÆšn>d°¤zü ––Oú £L4¹$@–-¹Gašj®Éf›n¾ gœrå£ ÿ`§&ÆQKx˜G—�%I�˜8$I*$ž€Ë-ØX‹ÞÕçKŵ¤GŠs‰ÝžXRThB¨Ušf{$tI�µøƒ€½„t.eÀ✶ފk®ºîÊk¯‰qÊ*aq”Ás%Ô*I¢í÷%iµ"¤‡ŒÅË“™`þ€R�2v!†Þz›]“%í9W¥.™ûÑ©uEªÐ³}ªP¨�JÚ˜5©«¥¦ -šP˜ ÙK’xè{$<úŠp /ÌpÃÆ®ŠÂÊèSÅO‘KÕhè’f"JWì¡BÕí!@Ç”AÊ*¯œÁÉ/ELÕ¤B6KQC%î¦2#KQ± JIo̦¢ÿzs�Ц֫°ÊÚ­? uÔROMõ›0—J’gÂåÓdÎÆ¡9#t¨B]çUdƒWcœÓÆ=[;Í1Á¬…§ÖÊ;eI2…ª”ó&Ì$QùRrUŽxâŠ/Î8LWïœxœ]Ð…Ã�GÇš–殈`+KyMc#ø¸K6m»Ü’në⺡Û]³”ÉRe¯M¨þ ïxªN4L©¾d`ãÂO|ñÆÃY:¨’ ZØ•hBZŽÐ{˜("e¨)âîFϳ–GŠ>n>€×¤S¾jÖ’C^ÀZø¦Ðfê€Q’”×M‘dji¡‡@“*3ÛAk3Ú# Kö€¦˜°ÅepaÞÿñÁJp‚v¹~0£Ü\PS{ÀÃèîŸ�äÁN^ÓÁ–.‚A�ý(!¸HXB®„„-B‰ÿ‚Ámé‚àIÎ#¹"$?8ÏŠ6Ñ|9éà="ˆ\ðQî¹îز!Ñ<‘*d [Æôª 8$%/�ËÍ'·AQp�ll£ßH‘…l/–©Lyó9X¹¤Ž¨+Ioá26é`k/ã㨣Ëô‘.J£ûLǶ0Ž”¬¤%/Ù0ákM—ÂdK´ÖÞˆ2”‚È#bô0G˜ôŽ(õ1Ô#= ËXÊr– D[ÚTb²yê¥/Ïx˜FÆk›¢Yghÿ‰Ìd*s™…Y¤ƒ.Ü13M°ùÁ â2‚Œ°dºœ¦7¿ Îpúj *I¥8¤…rªäeê\É9ß ÏxÊsžô¬§=ï‰Ï|êsŸüì§?ÿ Ѐ t -¨AŠÐ„*t¡ m¨C шJt¢­¨E/ŠÑŒjt£í¨G? Ò�Št¤$-©IOŠÒ”ªt¥,m©K_ Ó˜Êt¦4­©MoŠÓœêt§<í©O Ô  u¨D-ªQ�ŠÔ¤*u©LmªSŸ Õ¨JuªT­ªU¯ŠÕ¬ju«\íªW¿ Ö°Šu¬d-«YÏŠÖ´ªu­lm«[ß ×¸Êu®t­«]ïŠ×¼êu¯|í«_ÿ ØØÀ v°„-¬a‹ØÄ*v±Œm¬c ÙÈJv²”­¬e/‹ÙÌjv³œí¬g? ÚЊv´¤-­iO‹ÚÔªvµ¬m­k_ ÛØÊv¶´­­mo‹ÛÜêv·¼í­o Üà w¸Ä-®q�‹Üä*w¹Ìm®sŸ ÝèJwºÔ­®u¯‹Ýìjw»Üí®w¿ ÞðŠw¼ä-¯yÏ‹Þôªw½ìm¯{ß ßøÊw¾ô­¯}ï‹ßüêw¿üí¯ÿ à xÀ.°�Œà+xÁ n°ƒ áKx®°…/Œá kxÃâ ;scikit-hep-boost-histogram-5f6f3e3/docs/_images/ex_hist_density.png000066400000000000000000000524161516627434300256030ustar00rootroot00000000000000‰PNG  IHDR€à5ÑÜäsBIT|dˆ pHYsaa¨?§i8tEXtSoftwarematplotlib version3.1.2, http://matplotlib.org/%‹¡J IDATxœìÝtUõ�ïÿ×>'9ç$@¢ˆ$‚� Òª£åGê·àj¦±u]'�ø:Y.\Ó+­Ç[pÚÛiÓŽèJ§²¤t®µwÝqÉõ¶ZÇ:tœ´èí’– ²¦T¥µS šZ $çÇÞß?(G#Éç Ù9 ÙûùXë,å|ö�ÏÞçœ�÷ÙŸ½_Ç ‚ b#1ÖÀ袈 @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ @€˜¡ˆ™X€=ô�>ð�¨ªªJUUUjllÔ¿þ뿎u·JÊ ‚ ëNŒ•ù—Q2™ÔìÙ³�¾ó�ïè¾ûîÓK/½¤?ù“?ëî”D¬ ÀÁLžMœ8QO<ñ„.¿üòA§íïïWñß¾ïëÍ7ßÔyç�'ÏóF«Ë€q(½ýöÛš6mš‰Ò]‚ß××§l6z9©TJ™Lfz„³Q쇀³Ù¬öíÛ§îînýŸÿóôOÿôOzî¹ç-¿ð…/è‹_üâôû÷ï×…^X’e÷õõiÖ̉êö±�iÆŒzûí·õè£�jÛ¶múá8Ö]`Ø �¯BˆïÎG^¬ƒ :¤åË—ëýï¿>ò‘�èç?ÿ¹~øÃêOÿôOǺk ›¯ ôc86nܨúúze2544hÇŽCNûË_þRŸüä'U__/ÏóÔÞÞ>ètÐ_ÿõ_ë¼óÎSEE…®¼òJ½øâ‹ÃêÞë3€ÿãü�±î‘°e˵¶¶jÓ¦MjhhP{{»š››µwï^M�:õ”é�;¦‹.ºHK—.ÕwÜ1è2ÿð‡?èCú�®½öZýë¿þ«Î?ÿ|ýú׿ֹçž[ê͉¼X€D‘/?Ô՞Ùû�ЪU«´råJIÒ¦M›ôƒü@?ü°Ö¬YsÊô ,Ђ $iÐvIúÚ×¾¦ºº:}ûÛß.>7kÖ¬3îNEˆÒ)ñ݈Ö]º%½ÓÖ¼Ðޘݺ¿$ä�žæ¶ów„„}‡´AQâ»”ßX~˜ýò}mî;óæ�Wî„|ïrñøPB„|œé¼ÙlV;wîÔÚµk‹Ï% 555iûöíÃîÇSO=¥ææf-]ºTÏ=÷œ¦OŸ®ÿú_ÿ«V­Z5ìeâ„X_†ÖÓÓ3à1TJÆ‘#GT(TSS3àùššuvv{ýÿùŸÿ©‡zH³gÏÖøC}æ3ŸÑç>÷9}ç;ßö2q 3R7�ÔÕÕ©ºººøhkkÝíð}]}õÕúÊW¾¢«®ºJ·Ür‹V­Z¥M›6�j?¢ˆ!`"ÆW Â0ïä=9¿tâWKÞ=X®$M™2EÉdR]]]žïêêRmmí°ûqÁœòË\—]v™¾ûÝï{™8�3€`PUUUC€©TJóæÍSGGGñ9ß÷ÕÑÑ¡ÆÆÆa¯ÿCú�öîÝ;à¹_ýêWš9sæ°—‰8@Ä„Éò;9ÿ™jmmÕŠ+4þ|-\¸Píííêíí-Þ¼|ùrMŸ>½8ŒœÍfõòË/ÿÿÀ�Ú½{·&Nœ¨K.¹D’tÇwhÑ¢EúÊW¾¢¿ü˿Ԏ;´yófmÞ¼yØÛ†(ˆ˜Ñ¾ X’–-[¦Ã‡kݺuêììÔܹsµuëÖâ�!ûöíS"ñÎÀãÁƒuÕUWÿ½aÃmذA‹/Ö¶mÛ$�ˆŠyâ‰'´víZý÷ÿþß5kÖ,µ··ëSŸúÔ°· 'xAâs't{‰þŒßLؘ�Q!gs ŒiœÇÀXó[ÂÇÀ„<¬•0&ìü¡·-,b`†-ä´MßWww÷€ëêFÒÉ¿K¿z¥F“& ÿ8õöÛ¾ÞwYWIûбÅ@Œ��E”Yà…Ì[s.{Ìsö ÖüÖ¾+7 Æ�ÙÛûÇhù½ÔÌù3‹(ÇÂæô•¸€ ûÅÑàÿñf~D Syp˜y1>P1…àÄ#Ìüˆ6b`b†3€D ×ÂB@ÄøòTÐðï¦÷CÌ‹ñ�!`€˜á †¯ÔYxÖê�¨;K.DÔJÈ,7+fÅ+s·‡Ž‘1bVßê¿‘{i册�1û—0bxü¼{ýIÇü��c½­Ì}c¼·¬ùCÆÌØ12c›ƒˆÓã§‘vdÌ�h£ b !‡€ÃÌ‹ñ�!`€˜á Ã@X(ˆ?ðä׫Zó#ÚˆÎ1 ÃB@Ä”P!Ä Ÿ6„ Œ³1Îã2³ê¬ù­œ?kû¬ùAXVŽŸŒm r9÷ü3‡ÏÊÙ³–bßH2·ß쟱~¯`eÍYY}Fÿ\Y{Ʋ­ŒFsßû6lNŸ™Sös64ìq‰œAIRòÀ€k#�kb†3€D ×ÂB@Ä‚„ Aˆkù)¸Èc f8@Äøòä‡8Çã‹S€QG@Äp ,€ãY‰ãÂÆ´„�ƒ°ã.Œ¸Œò�°dˆþ[1$©”{~+%d”ˆRFŒŒÅ7Þ[f{ÈþeÃÅèÖgÃÕÿ„³b¯ÜÝì}3â9<ÏØ·Æ¶—:FÆúÜ€DLø›@(Ô£Ž€ˆ9q àðGAÂÌ‹ñ�»€b†3€DŒò·€¹ 8ú(ˆ®„…€ˆñ• N€g³°1/ÆüVÌJX¡—oõ?emŸ±ÿŒ¨¹¢V¬˜–²�-«oa·ÍRnô¿`¼÷¬×ÞŠ±Î>¤�+†'ŸwÏïzý¬m7…‚³Ý³bbrFNÒØ÷îÕ›1/VÿíãNȘ˜Ç_qA@ÄO#3ÒšÑF@ÄBÞR`8òˆ�ˆÎ1~��â.`Ÿ»€#�€ˆa†€ÀˆØ¸q£êëë•ÉdÔÐР;v 9í/ùK}ò“ŸT}}½<ÏS{{»sÙ_ýêWåyžn¿ýö‘îv,Q1¾Þ¹x8�á„élÙ²E­­­Z¿~½víÚ¥9s樹¹Y‡túcǎ颋.ÒW¿úUÕÖÖ:—ýóŸÿ\ßüæ7õ�|`=Ã`>›…Ìù+5Ï•“w:¬¬<ã”Ð9FÖ�çÚ¿IcÝÖ¾1ÛC.?dV›2FÎ^È,/kdáY×/™9‹Žö¼±o­œ?ëµµö­o´[9‚Ö¾1rø<ãs䌌ErGEø è3Ÿ÷�ЪU«´råJIÒ¦M›ôƒü@?ü°Ö¬YsÊô ,Ђ $iÐö“Ž=ªO}êSúÖ·¾¥¿ÿû¿?ã~apœƒêééðèïïtºl6«�;wª©©©ø\"‘PSS“¶oߪ·Þz«®¿þúËFx€DÌÉßó�¤ºº:UWWmmmƒ®ïÈ‘#* ª©©ð|MM�:;;‡½�=ö˜víÚ5äz1| 1¾<ùþ¯yœœwÿþýªªª*>ŸN§C÷ítíß¿_·Ýv›ž}öYe2™Q[o\P1ï>‹7Üù%©ªªj@8”)S¦(™Lª««kÀó]]]æ CÙ¹s§:¤«¯¾ú�~ zþùçõàƒª¿¿_ɰעÇX¬‡€ÛÚÚ´`ÁMš4IS§NUKK‹öîÝ;ÖÝ`\I¥Rš7ož:::ŠÏù¾¯ŽŽ566k™ùÈGô‹_üB»wï.>æÏŸ¯O}êSÚ½{7Å_H±>øÜsÏéÖ[oÕ‚ ”Ïçu÷Ýwë£ý¨^~ùeM˜0a¬»À°„‚>óy[[[µbÅ ÍŸ?_ .T{{»z{{‹w/_¾\Ó§O/^Ï—ÍfõòË/ÿÿÀ�Ú½{·&Nœ¨K.¹D“&MÒW\1`&LÐyç�wÊó8s±.·nÝ:àß�<òˆ¦N�ª�;wêÃþðõêôy®¨ …�K°–oÆ-qfŒKؘkþT¹»ÝbDa„Ž�±^Û2cßûÖšßËQi÷¾ó¬(ký9#j%0¢HŒ˜Ïzí]ýïü.ÈÓšW²c\¬ÏMʈè±äÝ1-A6jñVLŒ“øVáa¼7,¥ŒÏ:‹"füÀ“„¸pó.[¶L‡ÖºuëÔÙÙ©¹sçjëÖ­ÅCöíÛ§Ä»ŽMÔUW]Uü÷† ´aÃ-^¼XÛ¶mvßqzb]¾Www·$iòäɃ¶÷÷÷¸¾§§gTúÀx°zõj­^½zж÷uõõõv.¥± _¬¯|7ß÷uûí·ëCúÐ�§–ÛÚÚÜ_WW7ʽÀæÿqx¸�0!Òx…ÿèÖ[oÕž={ôØc� 9ÍÚµkÕÝÝ]|ìß¿{ÀéñƒDè¢�!`�8eýôÓOëùçŸ×…^8ätétzT3�J!Ö`úìg?«'žxBÛ¶mÓ¬Y³ÆºK„V�§Bˆ è0ób|ˆuxë­·êÑGÕ÷¿ÿ}Mš4©øs5ÕÕÕª¨¨ãÞ0%�¼-#çO®œÔì^ÊÊt‚l¸ Cë¸j溎›VqhþHH\[‡³ Ã/�ÀB@Äp ,€DŒ¯�?Ç5€‘G‰3œ b‚�wœŒ<Î1~à…~ ÇÆ�U__¯L&£††íرcÈiùË_ꓟü¤êëëåyžÚÛÛO™¦­­M ,ФI“4uêTµ´´hïÞ½Ãêâ àX²"ŒÈ�Ð12�‘G`ĬxåÆÛ§Üˆa1bhdÄIe Ì½|¿Â½|¿lèý[H»—�›dÄÄ»&Ÿv¿¶~¹»=;ѽü„‘fá/MÂJ 1æ·–Ÿ —"kö²cF{ÿÐû7ðÜ/^"ïþ\y…p9 žñ¹õ²Æ‹c}.ÍÇ�¼õæ02ެW/lNŸó¸kdÄ #î+N¶lÙ¢ÖÖVmÚ´I jooWss³öîÝ«©S§ž2ý±cÇtÑEiéÒ¥ºãŽ;]æsÏ=§[o½U ,P>Ÿ×Ýwß­�~ô£zùå—5a„RoR¤Q1cqð< U«ViåÊ•’¤M›6é?ø�~øa­Y³æ”é,X  HÒ í’´uëÖÿ~ä‘G4uêTíܹSþð‡Ï¸�x f÷äü’ÔÓÓ3àùt:­t:}ÊôÙlV;wîÔÚµk‹Ï% 555iûöíÃîÇ{uwwK’&Ožú¨¾ÿýïkÒ¤IÅë «««UQQ1ìíà˜ îÌ)/iåe¹™9}!—o ™3hæüU¸óÌü´•Å.Ë/[=t{vR¸?Kµ»½`\¦ã/M`¼5¬¿ V‚„gD¹™9ƒFÿSÝîö|…µÿ‡^A>ãÞ¸ô[F~g…{ç–ˆg¼ïÖçÎÈñórFŸñ¹ô¬ÏuÖ=ÊÈ)4¶/ð�7WÖ½}aòUϦâq, ÀeË–éðáÃZ·n�:;;5wî\mݺµxcȾ}û”H¼óù9x𠮺êªâ¿7lØ  6hñâÅÚ¶m›$顇’$-Y²dÀº¾ýío릛n:ã>â€DÌX€Ò‰kõV¯^=hÛÉ¢î¤úúzVAo}¡Á°q @Ìp€ˆ ¤P10œw‹> @"f¬†€1~0 3œ@’¼„w�œ„…px OÞ`Ñ žq‚Õ8ИqVŒ‹÷ #.ÁËY"åF\C*ånO¸÷O�±bb¬wÿ#ÆÕRH»÷m>ãn/dœÍv»ñÒä&–ö žDÎØþjwÄQâ¸{ß'Œ$ùÆ{;ä߮±¡Pvܘ׈‰ ŒÏµŸrÏŸêvïkù‰¬ûµ±Æ…¼·�¹'°ŽKÉc®Í3v­?Žìò·Ýó—[)+}îÏ…oÄ#•çŒ H¹?×^ÚÈ òÝï sþ¬s“ͺ緎[Îãªû}XÛfs¼À³V1b¸–X_ØÛÛ«9sæhãÆ�cÝ€Që3€ûØÇô±�}l¬»Àˆb–X€DCÀ°Pž�þþ~õ÷÷ÿÝÓÓ3†½žX_x¦ÚÚÚT]]]|ÔÕÕ�u—8EðÇ!àá>8}€g`íÚµêîî.>öïß?Ö]à�¤ ñë @É1|Òé´ÒV„`\ò’I…QÊiÆX¬ À£G�êµ×^+þû·¿ý­vïÞ­É“'kÆŒ§½œÀx§~_23¡Œv‹gåü2¯Ìxù“î¼1ÆwD#g00òÆü”‘wVî^~ÁÈùËY~ùŠ¡Ûs�³*7ÉÝî»ÞÌù›è~m+Ïu‡ÉYQi–dÂÝ?o�ÏûYÃKÙœ{eûŒxŽ»9÷¶;C²P9ô{ÏO…ËùÓ¤p‡e}FÎßѼ³Ýϸ?Weo¹sö#ßÓ³òE­+äq1,Ï:î9XÅc`3G�/O¿‡X€/¾ø¢®½öÚâ¿[[[%I+V¬Ð#�<2F½ î†%Öà’%KFõÀÙ Ö QäÞ‰Ÿž 1?¢�€ˆ9y7o˜ùm€D ×ÂB @Ìppx O^Ø\�a­Øˆ™I¥Üó[q V‚‘‰hżeÆú�íóËŒ—J#Æó"I…Œ£Íˆƒ,»>Ž…QáŽòHOtGuLH»Ûs¾ûµ�˜îw¶OJ¹ÛFŒl_!Ü¡ç÷ÝUÎvëìE¾Ìc“ó†~�ó9÷¾KäŒÏ¥ñÒûåFß�ø"ë°^îÞtåÏ©p/½ÇýÚ›Ç�¤Ñßh·–&fÆ7Æ=£ogÎÂB@Äp, Ä gˆî†…€ˆ9Q†¹p;ƒ³CÀ1Ã@"†»€aá `)yìÞ¨ÊU1>Wf„¢kÓƒx ÇÆ�U__¯L&£††íرcÈiùË_ꓟü¤êëëåyžÚÛÛC/§�3€¥æ€áÊ«’Ì‹4#ËK•‡[¿ÅÈó ŒÂ|¥ûí™­vç�å�¬>ߨüÜD£ÝQæ«Ýû¾ü\w–ZY™{þIîùgVýÁÙžÜûþüôQg{ΘZºÛÙÞ“wgÍì«v¶Ëhî7r�eÝA�os¼9r gèFN�qf%éŽpT!eä Z1z)÷k—ÈŸ[#¿Ó<®˜Y{îvÏÊ 4Ž{AÞ�±irÓ½„»o^àIþþ°e˵¶¶jÓ¦MjhhP{{»š››µwï^M�:õ”é�;¦‹.ºHK—.ÕwÜ1"ËÄé?_gÀi99æq¦xà­ZµJ+W®Ôå—_®M›6©²²R?üð Ó/X°@÷ÝwŸþê¯þJé!~XàL—‰ÓG@ÔŒÐpOOÏ€Gÿà£ÙlV;wîTSSSñ¹D"¡¦¦&mß¾}X›PŠeâ€DMس<XWW§êêê⣭­mÐÕ9rD…BA555ž¯©©Qggç°6¡ËÄ;¸ jÿþýªªzç··‡ªÅøC@ÄŒÔ/�TUU (‡2eÊ%“Iuuu x¾««KµµµÃêC)–‰w0 @ÄŒöM ©TJóæÍSGGGñ9ß÷ÕÑÑ¡ÆÆÆamC)–‰wpp,Y1žñ4Ú½ò�/o™1¿óûåîí·Ž?VÜ…_î^@®Òšè¶BÆøj�rw.ŸuG…\0Ù£r~E¯³ýÒIîëc’ÆÎë32rÞ—q/Rò¸³ýwÙ)Îö # ¥'�q/¿w²³}RÊ£ÓÛ7tLL§û�c½o}w� F»o|,“Y#Ê8ì!ã§T0>˜Öò­˜+F&aÄÔ$‡þì¾;"Æ5¯$ùY#‚&⿯ÖÚÚª+VhþüùZ¸p¡ÚÛÛÕÛÛ«•+WJ’–/_®éÓ§¯#Ìf³zùå—‹ÿàÀíÞ½['NÔ%—\rZËÄðQ5ﺑcØóŸ¡eË–éðáÃZ·n�:;;5wî\mݺµxǾ}û”xW�~ðàA]uÕUÅoذA6lÐâÅ‹µmÛ¶ÓZ&†�Ãí“ IDAT€ˆ©kÏÔêÕ«µzõêAÛNu'Õ×ןÖYQ×21|\3œ jÂü ïÉùi€DÌpÎíÝó#ÚˆΖ�gÅXí#îÀdDX13A¹{þ m,߸ø×O¹×ŸÏý3VoÅqä+‡îŸ_éŽ{ÈT¹cFfLþƒ³}rÚ£2©¼ÏÝžt·'Œ˜Tìs¶'=÷k—ñÜ1.ÿ_寜í;õÎößgÏq¶W$sÎö×{Ïs¶WU ½ÿÞ8×}Ø,”¹#t$÷3y܈/šän/7æOñS‰¼#“ro�2¢TŽ»?;æq-i´»W¯À3cE[åÂÅÄx�oöoD1Œ  @"†!`X(ˆn��kb†3€DŽ÷ÇG˜ùe€D CÀ00 3œ j8` ¾û䕹¯±°2¥¬œ>³=¤À蟟6Þ^!s+,m´WÝ^Þ½r+B¡»¯ÂÙ>s¢;'ðœrwNà…©7�íåž;ŒlzÙ[ÎöŒ1_îÐr~Y�±~wÎß/Ž]èl÷�×Çw\ÿ”Hº3 eF¾eÚÈÙKYtÆûÞ7Ž+2>å½îöÀ:®X9~a�[F~¨™˜~ece»ZÇüQx'aæG¤1 3œ b‚À>YjÍ�h£ j¸†€b†3€D 7�À@@ÄxÁ‰G˜ùm€%dƸ˜qîùCÇÄXŒ8ÏwÇa$úÝQ!¹*wE¾2Ü f‰±ûýÔÐeîm¯Hg�íuUBÈoßýÎösÇœí• wÌJÒØy5I÷ú»Ý»O‡Œ˜—·åŽÑ™‘vÇàtöU9ÛËCw°Ìxís¾ûµK7"„¬Ã†»Y¹ ÷ú+޹ûï14Öç>(7Ž[寳ÏýÞQy¹»=ë~ï8�‹ã�i°bb¼Ñ<«Æ5€0p @Ìp€¨á@(ˆ†€a` f8@Ôp @¢††€K)0"|÷',Ⱥ£Dkù#²@…B¸åGX"ãÞ7�qõÇ+�í³*Þp¶×§;Ûk“ÝFûQgû�×~(åeî}“¬tÇ�*Œ#&a¼4V~›Ÿr¿7þòWÀ1ãgG€—LÊó†8j»Š4/á.“ž³ô2igèÉøK"…*ƒ2wŽ_`äúåÆº�¿C…´•Wæž¿¬ÏÝž=×±l•¸¬ßEàÔä$g»/÷º­œÂT¹;cò˜Ÿr¶¿b—³½¯0tÖÜkýSœE`VeÎ"08žv�ÉþÄø.­|SKÒ8/Ñïþblqå§!sO,ÃqÌ F±@ä.`(ˆ~ –ØoܸQõõõÊd2jhhÐŽ;ƺK%ëpË–-jmmÕúõëµk×.Í™3GÍÍÍ:tèÐXw €á Fà�H‹uøÀhÕªUZ¹r¥.¿ürmÚ´I•••zøá‡Çºk%Û0›ÍjçÎ�jjj*>—H$ÔÔÔ¤íÛ·:O¿zzz<8Ûxzç:Àa=ÆzPr±-�9¢B¡ šššÏ×ÔÔ¨³³sÐyÚÚÚT]]]|ÔÕÕ�FW£!l´0ŽpðX»v­Z[[‹ÿîééq��‘ó—ȸwà‡Ë”#î@’乿ãiw”†¬c¥ñ2‘w÷¿ì¸{¹J#¦àn/¸SlTÖ;ô÷£\…{ßzÆ-t5•î?ä÷ï·ü gû¡‚;¦eQæMg{Òè_gÁ½}㣺²^gûïòá¾»vöW9ÛóþÐË·rËÒîö¼o|îŒMó‡N¨9ḻ9‘uï{Ï8®ø)÷{?iEÈXQ+Öq-oD©X12Öq5LèYïËQ<ç2F107nÔ}÷ݧÎÎNÍ™3GßøÆ7´páÂ!§üñÇuï½÷êõ×_×ìÙ³õµ¯}MÿøÇ‹íG�Õš5kôä“Oê�7ÞЬY³ô¹Ï}Nó73¬þá±=8eÊ%“Iuu ÌëêêRmmí ó¤ÓiUUU xpÖƒ›@ÎôÆÊ^xA7Þx£n¾ùf½ôÒKjiiQKK‹öìÙSœ¦µµU[·nÕÿú_ÿK¯¼òŠn¿ýv­^½ZO=õÔ™wĶL¥Rš7ož:::ŠÏù¾¯ŽŽ566ŽaÏÎôÆÊ¯ýëºîºët×]wé²Ë.Ó—¾ô%]}õÕzðÁ‹Ó¼ð Z±b…–,Y¢úúzÝrË-š3g‘m# ¶ tâ›Å·¾õ-}ç;ßÑ+¯¼¢Ï|æ3êííÕÊ•+Ǻk ß�|ï��ýýýƒ®n87Vnß¾}Àô’ÔÜÜ<`úE‹é©§žÒ��~üãëW¿ú•>úÑ�žáÁ{ÅúÀeË–éðáÃZ·n�:;;5wî\mݺõ”COFê—@Þ{�ûúõëõ…/|á”é]7V¾ú꫃®£³³Ó¼óßø†n¹å]xá…*++S"‘з¾õ-}øÃÆVáÝb]JÒêÕ«µzõê±îg�ýû÷¸Þ=�6îžaßøÆ7ôÓŸþTO=õ”fΜ©çŸ^·Þz«¦M›vÊÙCœ™Ø€DNØ_óøã¼§{Ããpn¬¬­­uNüøqÝ}÷Ýzâ‰'týõ×K’>ð�h÷îÝÚ°a`H€# ðƒœk÷F܃Gà%B^¢Y0âRî˜ÏŒ¡q·I#FÀJ‹HÛ2©4‘s·ç' ÝÁDʽo“ ÷Æ:>ÁÙžINq¶OœÔçlÈy™›þÎöƒF„δ¤{ûÞ2¢N*�±©·wÔÈ�ü¹Îö\à>´•{î÷î±¼•µ2´þ·ÜgH’ƶ'û¬x#÷ú­‹_æ^@¢ßÝ/›w¯ 0>øV»q\U.Äò­è,+¢&0*£™38Bàéz÷�•---’Þ¹±r¨Q¶ÆÆFuttèöÛo/>÷ì³ÏoÄÌårÊårJ¼çoa2™”oý}‚‰„ÖÚÚª+VhþüùZ¸p¡ÚÛÛÜX¹|ùrMŸ>]mmm’¤Ûn»M‹/Öý÷߯믿^�=ö˜^|ñEmÞ¼YÒ‰³�‹/Ö]wÝ¥ŠŠ Íœ9SÏ=÷œþçÿüŸzà�Æl;£‚€ˆ©›@΄ucå¾}ûœÍ[´h‘}ôQÝsÏ=ºûî»5{öl=ù䓺âŠ+ŠÓ<öØcZ»v­>õ©OéÍ7ßÔÌ™3õå/™ è@@ÔŒÑ/�¸n¬Ü¶mÛ)Ï-]ºTK—.ryµµµúö·¿=¬¾À�€¨åk1þÄ: Ž8@ÄŒÅ5€_(�NļX13ãCÀ0PŽ%+ïÊ y ²r’rFfU™‘‰’_îÞ¾DÞ½9#/ÍÊ̸Û]ykÁq÷Gç­·*�íçMëu¶ûÆØý¾;§.)÷k ïv�š<êl-gl¿_ál?'qÜÙ^n„Ýe¼¬³ý˜ïθ|3ç~}ÞîúÍÑ—5›ÖÅóV»Õl|¬“Y÷?° 㸔ȅË }\ {\´¸²þF3Ç(1 @¢&ä0g£�€¨aîˆÎ5œ„�€ˆ!†€b†3€%ÜQ^ÒˆC°bb®ߊS°ÖoÅEô¹×Ÿ,wo!ãþ~XiF÷Ë{Üíý罂„µaD}t½=ÉÙž+¸7®¯àŽ�ÉU¹çŸ‘zÃÙþfa¢³}vªËÙÞcÄÀŒïžoäÝëïÊW;Û;ûÝíVÌÎñìÐû7gDà$úÜÛæéK £ÝHÈQ"ïn/?j|.ûCF�X³g­ ób·\Ç]ã˜ä8-†è  j¸ @"†kaá@€˜á QÄY<8P5\CÀ1ÃÀRòŒúÚ·â Œå[1/©Œ{~+.!ïΓðrîz)w„_îŽ{(;fÌ_æžßOºÛ­×EÐÉ·Ý#©C}wŒKÚ½üTÒýÚʺcfú|÷úg¥;Û_8v‰³=a\Aþ«üg{e"ëlÿÅÛ:Ûó�û³·ÿè9ÎvWLL!gÄAeÇÃ}ïN÷¸—Ÿìs·[÷{ÆqÉë3Ž ¾‘c·dÎ2¦ÆZ¿ƒ—0Ž)¾õÚŽÞ9n�…€¨a†€b†3€D CÀ°P5 ÃÀ0@Ìp€¨á €H'"b’VþÖøÀ5€°PŽ„À—4H6••„̳²XyY�;kMeîvÏ�‰ååÝëOö¹ó¸rÕdÖ}„Jä�ö¬»ÿå=®V#ƒ°ÜýG$Ÿu·÷çÝÍÿì>ÏÙ~áÄngûïƒj÷ú}÷úß̺sü*’î÷VÞa,óÜï�cù”³ýטâl÷}÷ëwìhzèÆ7ÝëNõ¸?÷eG�Í*ëw·Ü«W"çn7sþrc}\²òQ�ãjÖ½ü lNáxÁ@¸ f8@Ôp @"†kaa f8@Ô0  Ã0,€g± ëŽÒð*Ü1-�gà%Ý1+¦¼{ùA…ãr,ïl÷ÓF”J…·Ñç>‚RFŒ�có’Ç�³Êºº"—pgy¼e|Óî¬�dÂu‘/¸÷í}•Îöó2Çœí~àÞ·�Ç'9Û-ow¿÷û³îCÛñ·Œˆ£ÜÐýO2ž‘2b$ä(a´'�˜˜ò^wFLŠÅ “bżäÝÇë¸frÅÐÑ]溭h¯RGg€kˆš`ðqãFÕ××+“ɨ¡¡A;vìpNÿøã�ëÒK/U&“Ñ•W^©gžyæ”i^yåÝpà ª®®Ö„ ´`ÁíÛ·oxD Q3à–-[ÔÚÚªõë×k×®]š3gŽš››uèСA§á…tã�7êæ›oÖK/½¤––µ´´hÏž=Åi~ó›ßèšk®Ñ¥—^ªmÛ¶é?þã?tï½÷*“Éœy1 í�ЪU«´råJ]~ùåÚ´i“*++õðÃ:ý׿þu]wÝuºë®»tÙe—éK_ú’®¾új=øàƒÅiþîïþNÿøÇõÿðºêª«tñÅë†nÐÔ©SGk³"‹€ˆñFà!I===ýýƒ_„šÍfµsçN555ŸK$jjjÒöíÛ�gûöí¦—¤æææâô¾ïë?ø�Þ÷¾÷©¹¹YS§NUCCƒž|òÉ3ß!8 Q3BCÀuuuª®®.>ÚÚÚ]Ý‘#GT(TSS3àùššuvv:Ogg§súC‡éèÑ£úêW¿ªë®»Nÿöoÿ¦O|âúó?ÿs=÷Üsg¸Cð^Ü @ÄŒT ÌþýûUUUU|>�N‡ìÙéóýwMÿÙŸý™î¸ãIÒܹsõ /hÓ¦MZ¼xñ¨õ%Š(�aHöI®A¢%ðÍ(˜¸©ªªPeÊ”)J&“êêêð|WW—jkk�§¶¶Ö9ý”)STVV¦Ë/¿|À4—]v™~ò“ŸœÉf`€# ðƒ|ÕòÜQk6+“ªÏ楬œ?£=ëΚ³ò¼¼œ±|Ï�§–è7r Óîýcå¡ùåîõ»ÚsN�C ŒOVò˜»ï¹s�gÝo®ß9x™Œûµ ÷·ü®nwŽ_U…cçH:Úç^~ÁwïŸ|¿{ûýœñáË»_ûäÑ¡ç/Û˜×Ȉ,sï•÷º?Wasþ<ßøÜæ­,;ã´’q\²>÷fÖž+Ço$¸²úÆSŽß(ÿH*•Ò¼yóÔÑÑ¡––I'ÎàutthõêÕƒÎÓØØ¨ŽŽÝ~ûíÅçž}öY566—¹`ÁíÝ»wÀ|¿úÕ¯4sæÌ3ë NA@�ò¯y´¶¶jÅŠš?¾.\¨öövõööjåÊ•’¤åË—kúôéÅëo»í6-^¼X÷ß¿®¿þz=öØczñŵyóæâ2ïºë.-[¶Lþð‡uíµ×jëÖ­ú—ùmÛ¶mt7.‚(@hË–-ÓáǵnÝ:uvvjîܹںukñF�}ûö)‘xçìþ¢E‹ôè£�êž{îÑÝwß­Ù³gëÉ'ŸÔW\QœæŸø„6mÚ¤¶¶6}îsŸÓûßÿ~}÷»ßÕ5×\3êÛ5^Xçó1”žžUWWk‰÷ •y§wzI÷0”—p…„½Åö*Œ‹ØŒþ«Üýý!¨šànO¹çÏOtƒæªÂý”]ÿ9îíë¯r»ä'í•ÆÏÔUCMîa²d…ûç´ì!`÷ê­!Ú³~8ë^~òíÒ §»ÝíæðQ÷{£ì˜û½QvÜÝž8î~ï$Ž—ž5v€1„sÿÌ 9ìËw]Úb ñ†ýº|�Ó� ßSww÷i]W7'ÿ.]qËW”L ÿBåB¶O{6ß]Ò¾blq€¨åk1þÄöv§/ùËZ´h‘*++uÎ9çŒuwFMl Àl6«¥K—ê3ŸùÌXw€u20ÌÑÛ!à/~ñ‹’¤GydÌúXq VŒŒu ¡}!—»ÝºÐêÿ1#Âè_™‘4®›àî¿u-U¡|èå{�±ï ~™{~/ïî{Áˆ1ñ�º?ÚÇÎ5"†òÆwCã¯Ã[÷üÙ·Ý×wz ã½{ܽýîõ'Œ„£²£Cïßò^÷¼åGÝíÉ~÷¶%rÖ5€ÆõŸ}Æ5|}Æ5€}Yg»wÜÝ®œ{ýÊ;?dÌ‹y�žã¸ûÞ¼.ûlЉa†Ø€ÃÑßß?àw{zzư7ÃÛ!àáhkkð›ˆuuucÝ%NÁ0,‘*׬Y#Ïóœ�W_}uØË_»v­º»»‹�ýû÷�`ï!Á<i‘¾óÎ;uÓM79§¹è¢‹†½üt:=ª?„ À°p ‘*Ï?ÿ|�þùcÝ €³Z¤ À3±oß>½ùæ›Ú·oŸ …‚vïÞ-Iºä’K4q¢ñ3œÅÂ^ÇÇ5€ÑÛpݺuúÎw¾Sü÷UW]%Iúñ�¬%K–ŒN'ÌÈ€p1,2¢F,A¿ñ“Oãg†òî8/o¼ý²Æü¾;J¤ü¨{þÜD÷ú3úõÉN²â Üû¾ÌýKiÊ¿¼”4~ʬ`\©àçÜxI÷{+06?ßmü a¹{ùÉ£îx#FÇøhd¹Û]FÊIù1#Æ¥×ý¹Ow·{F|’W0bfú­cçŸk%Œ7‡uܲ⟲F �õSs!Î͹lcÛFõ—W†!R7�œ‰GyDAœòµâ`ŒÄö Qå�y¶ØšÑF@Ô0 Cl‡€âŠ3€D wÃB@Ô0 CÀ¨´1ÀÙ†3€#ÀKœø�á÷²&^ÒÈù³r=wýXy]/åÎÙ“u°,3¶¯Ï�3hå‰%{�<³d¸DWN`êm÷kã—}76ÝÊÌWºgO7–Ÿ0²ÖŒvßó'Ïzë†ü;ëYQtF»•ÃXÖ7ôé�d¿‘ahÄÔ%rFN_ÞÈüƒ»ó^ÎØ¹y#gð¸ñæ´rü¬œ>ã½%cûòîTϱþ à>n»æ=Û0  QÃ0 €D gaá@€˜á QÃ0 €Døpa f88?P0ÈW-+2ÀΜrǨxFÊŠÅ\¿#c"øFÖ†3ãeÝ1/I#Ÿ�v¶'úÜÛŸ,úû‘WîÞú²ãîö„‘`cŨ”÷ºÛóáb\ã½eŸ˜11ÆüÖö[1/Iã­g ㌗£îÎ'�ocùò�˜ãs%ßxqŒãF`-ßbô?¬’fýYÑ]VûH ‚p‘9!ãvpö£ b¸ †€b†3€D wÃ@@Äx¾}-­5?¢�!`€˜á QÃ0 €À0”Í+7‘�%^ÂSàG£òá.`Xø 6_Ò©L~iGØ­<+ÏÈ4/°ò¾’îx # ÎÊû2r•0Öß—u·'Ý9�e½Cç™e wž›‘é—)ŠFΟÅÊɳrúÂ.?0Þ[~Ò½ýÉ\¸¿>eÇ�¬¾×7•õù‘ýî…'ú�ÏmÎhÏùœV~[_¿»ÝøÜ9#çÏxïËÈ ¬ã‚u\² 8Ïñæ |g~ë¸*É„�kÀˆØ¸q£êëë•ÉdÔÐР;v8§üñÇu饗*“ÉèÊ+¯Ô3Ï<3ä´ó7#ÏóÔÞÞ>ÒÝŽ% @"æäp˜Ç™Ú²e‹Z[[µ~ýzíÚµKsæÌQss³:4èô/¼ð‚n¼ñFÝ|óÍz饗ÔÒÒ¢––íÙ³ç”iŸxâ ýô§?Õ´iÓμc QŒÀã =ðÀZµj•V®\©Ë/¿\›6mRee¥~øáA§ÿú׿®ë®»NwÝu—.»ì2}éK_ÒÕW_­|pÀtÐg?ûYýó?ÿ³ÊËC^¿‚" @0¨žžž�þþÁ¯!Íf³Ú¹s§šššŠÏ% 555iûöíƒÎ³}ûöÓKRssó€é}ß×§?ýiÝu×]ú“?ù“Ø"œD@ÄŒÔp]]�ª««‹�¶¶¶A×wäÈ ÕÔÔ x¾¦¦F���ƒÎÓÙÙiNÿµ¯}MeeeúÜç>bo`0Ü @ÔŒÐ]Àû÷ïWUUUñétÚ�ž0’vîÜ©¯ýëÚµk—<ϸ»gŒ°”#ÎÀGp:ó[«w§%È ŒTÒˆ‘)¸ûÜq^ʸ–ÃúÀ7/‘q¶'z�8 ‡Â¤ g{™+"F’_nDØdÜíVÌJÒ�€£BÊX€µë�ÙFŒ‹Ÿr¯ÀŽqq·›oí>÷{7™u´ëvÅ�˜ß½n¯ÏÈØ±¼mdYñJyãÀa±^+æÅø\‡Žb s\ yL�ªªª€C™2eВɤººº<ßÕÕ¥ÚÚÚAç©­­uNÿÿïÿÕ¡C‡4cÆŒb{¡PÐ�wÞ©ööv½þúëg¸5x7†€ˆ˜Ñ¾ 8•JiÞ¼yêèè(>çû¾:::ÔØØ8è<���¦—¤gŸ}¶8ý§?ýiýÇü‡vïÞ]|L›6MwÝu—~øÃžYq Î5cðSp­­­Z±b…æÏŸ¯… ª½½]½½½Z¹r¥$iùòåš>}zñ:ÂÛn»M‹/Öý÷߯믿^�=ö˜^|ñEmÞ¼Y’tÞyçé¼óΰŽòòrÕÖÖêýïˆ�ƒDFÀ²eËtøða­[·N���š;w®¶nÝZ¼Ñcß¾}J¼ëœ-Z¤G}T÷Üs�î¾ûnÍž=[O>ù¤®¸âŠ±Ú„X¡ bÆê·€W¯^­Õ«WÚ¶mÛ¶Sž[ºt©–.]zÚË线‘C@Ôø�yCŽ9?"�€¨ƒk1¾P–’ób°â¼Dis‘‚¬‘%bÄÄx©”{ù9w…õ!#æE½ÇÝËO»û§²¡·/iDÈ$ú�—r÷¾+;êž¿Páþè&ûÝQ¹‰îù­á+fÅšß/s/À+X10îå'�(k~Ïqäå­ãsc­»ß˜ßj·²ßŒ–ÀЉ)¸ß[�ÑöÌ’uÜ3×nåîöÆÄ`ü¢ b<…¼pÄz‚³ Q3B¿‚è" f8@ÄŒU Æ @¢†»€a` f8€NDo•:^k´xA /Ä�aæÅø@XJV&TÈL©ÀwÏï¹£æÂç Zy`FŽ —N»ç7–ïõ»³øTf¼½�¹s•*w¬;瞷½mž‘S§¤{ß'²îm³rSo¹÷�ŸrÏï ëú¡¤ñÇ%‘5Þ[ ÷{?‘³²èŒ,¿ìЯ�gôMIãs�5Þ;as쌜À’æäI’gPî×>lÿ¬ãZ©ç?kø23'Íùi Ä gˆ†€a¡ j¸  @¢†_��kb†3€D ¿K, À×_]_úÒ—ô£ýH���š6mšþú¯ÿZ÷w§T*5z cÆ´„\¾“4rfŒù­˜SÞˆRqĸH’gÅ𸔻?:Þq+¢ÆÚwÆkŸ7"‚Œ‹µ|ÏŠY1¢@‚²pÛçŠi‘$Œýcì/çX¾54Ö{ÌÝnDØ(äç"°>F޵}�kßHöq',cùÖq±ä18g †€aˆeøê«¯Ê÷}}ó›ßÔ%—\¢={öhÕªUêííÕ† ƺ{%Ëðºë®Óu×]Wü÷E]¤½{÷ꡇ¢Œ{žâf~D[, ÀÁtwwkòäÉcÝ Âc @I¯½öš¾ñ�o˜gÿúûûÕÿ®Ÿëéé)u×F\¤b`Ö¬Y#Ïóœ�W_}uÀ<Ðu×]§¥K—jÕªUÎå·µµ©ºººø¨««+åæ0<Á<i‘:xç�wꦛnrNsÑEÿÿàÁƒºöÚkµhÑ"mÞ¼Ù\þÚµkÕÚÚZüwOOE à¬ÃOÁÁ©ðüóÏ×ùçŸZÓ8p@×^{­æÍ›§oûÛJXÑ ’Òé´ÒétØn" ú³Rz#ƒpöÈå¤rwÄœí‰zã”TIDAT"Už®hÉ’%š9s¦6lؠÇÛjkkǰgïaä]FVœÅK„»Í+°¢à¬œ@릑%g¶[9„ro€—w,?‘8QºÚ]òƾ±ŠK#ëÍë3Öod¥Y¯]�6r�œ>ï¸ñÚ[YmÖkoíŸ�”Êå†n³ún´®÷•dæø…Î¹32òòE­þ›9Æò�ù§¥Î0MÜC, ÀgŸ}V¯½öš^{í5]xá…ÚÞô€ñ.�¦žåOaäEê&�ÓuÓM7)‚AŒw'¯ ó@´Å²ˆ³Xi�B^8b=ÁYŠ€¨á&ˆÎŽ%WÁé°" Œå›q 3F&°bZ¬¨cñFÔ‡·áQ-aöŽ2ÆÅ3"IeÆG×úd[I!Y÷ú½þ�=Ö{ßÚþ¤5¿±�(çÙ��gF̘–°1.Öú­×ÖŒo •rþ°Ç­HE½¸ø’Œ�¡9?"�€ˆá—@`aŒˆ�7ª¾¾^™LF Ú±c‡súÇ\—^z©2™Œ®¼òJ=óÌ3Ŷ\.§ÏþóºòÊ+5aÂM›6MË—/×ÁƒK½±@@Ôœ¼ $Ìã mÙ²E­­­Z¿~½víÚ¥9s樹¹Y‡tú^xA7Þx£n¾ùf½ôÒKjiiQKK‹öìÙ#I:vì˜víÚ¥{ï½W»víÒ÷¾÷=íÝ»W7ÜpC¨]ƒ¼€ôãaëééQuuµ–èÏTæ ã·AÇøÀ°¬Ÿd2¯%²úg,ßd\‡f]h^gçZ¶u  u Zؾ•ûÞbý˜õÚp àг[×ø×è™Ë·~*nŒ¯´¶?ô5~fÆîâ¶|�Ó6}_ÝÝݪªª*É:Nþ]úÈ嫲äð»>_èWÇËΨ¯ Z°`�|ðAI’ïûª««Óg?ûY­Y³æ”é—-[¦ÞÞ^=ýôÓÅç>øÁjîܹڴiÓ ëøùÏ®… êw¿û�f̘1Œ-ÃIœ jFè `OOÏ€Gÿ «Ëf³Ú¹s§šššŠÏ% 555iûöíƒÎ³}ûöÓKRssó�ÓKRww·<ÏÓ9çœs¦{ïAUWW§êêê⣭­mÐéŽ9¢B¡ šššÏ×ÔÔ¨³³sÐy:;;Ïhú¾¾>}þóŸ×�7ÞX²3¨qÂ]Àc©ÔC!‡ˆ­!Þ°13fŒLØ4 c(Ë*ó\Q-Ö²û³ÎvsÕŠ°É;'ômÈáw‹5›2†Ð�÷…\¿û½iÓºXóZCÈÆ¾·â�cøÛú\ùpCÐsˆ7.1-¥6B10û÷ïPl¥ÓÃV#—Ëé/ÿò/�zè¡1éCÔP1#SUUuZgÛ¦L™¢d2©®®®Ïwuu©¶¶vÐyjkkOkú“Åßï~÷;ýèG?âìßa„’J¥4oÞ;`ú“Å߯ýkýû¿ÿ»Î;ï¼Òl@ q€¨ƒßnmmÕŠ+4þ|-\¸PíííêííÕÊ•+%IË—/×ôéÓ‹×ÞvÛmZ¼x±î¿ÿ~]ýõzì±Çôâ‹/jóæÍ’Nñ¡]»véé§ŸV¡P(^8yòd¥¬KEàD@Ôø�ä…(‡dzlÙ2>|XëÖ­Sgg§æÎ�«­[·oôØ·oŸZ´h‘}ôQÝsÏ=ºûî»5{öl=ù䓺âŠ+$IÐSO=%Iš;wî€uýøÇ?Ö’%K†¹q�È %tàX+ñM öêK|£�™C"'0ä²Ãfza3ÇûM Yã&sýÜ2äü¥þ-âß2š9€Mß:ðßÓ^Ò¾blq€¨ƒ!`Œ/€DNÈP€QG8žYÀÖPŠÑX#AÆõ%ö¯•Óîä…  \üÖf©YCÐÖOÉ…^¿±|ë§ä¬ù�?³þ¼—õÞ1†Q�¬!Ú°CÀ!*Íâ-ñO­ÅyˆO(ˆ†€a  jü@¡†qK|¦c�€¨ üpÃí ÕG¿3œ j¸ @¢†ka ÏJ}�FÈ_ú°â ßøÅ+êÄŠ±ñÃýÒ‰ë̾…eŤ„Žqï;篠Hf Ž…âùF�5¿Ñ¿°ÛWÒ_Ã(õ/m„Œ‘€D CÀ0P5�B€#Öœ¥¸  f8@Ô0  Qãû’BÜ(8Ö¿wŽ’c f8 Gà;cr‚BÁcÌ�³X¡ ¹^[« Ã@ˆ¡…Í ›#hfÁ¹süÌœÀPqlÉÓXþÐ+ðž{~£oAÁxm¬×.d�äÝŽòŒŒÅ°ó;2OGèýgeù9ÚÍ>kÝgû~Cöì@ QÃ/�Àp–•ÀHã ¾‚ÃñaæÅø@@ÔA¸a\®Œ<†€b†3€DMò&ÎF Jg¼ÇY9a¾µíá–ïÌ ÔiĨXÛg¾vî?fÄ�Õÿ�AfK)…½¶ÊŠ »m\û ¾/y!^KÞ‘w–ÿÀHã QÃ0 €DLàû B } Ä gˆ†€a  jü@ò(14 @Œ��טVRŠUbF…8¢F¬yM%¾¾&È»—oƬXQ"AÎÝnÅÄXQ&Fÿ­å[11Öö¹^ß1�� \ÛA )L Ì8ŸÃÄ5€1Ã@"&ð!†€ÎF^lÏÞpà š1c†2™Œ.¸à}úÓŸÖÁƒǺ[„øáðqãFÕ××+“ɨ¡¡A;vìpNÿøã�ëÒK/U&“Ñ•W^©gžyfàf�Ö­[§ .¸@jjjÒ¯ýëaõ Ŷ¼öÚkõ¿ÿ÷ÿÖÞ½{õÝï~W¿ùÍoôñcÝ-Æ¥-[¶¨µµUëׯ׮]»4gÎ577ëСCƒNÿ /èÆoÔÍ7߬—^zI---jiiÑž={ŠÓüÃ?üƒþñÿQ›6mÒÏ~ö3M˜0AÍÍÍêëë­ÍŠ,/à<¯$é©§žRKK‹úûûU^^~Zóôôô¨ººZKôg*óNoŒ¢°7�8.ö}È HÈßy{#BØ›L¸ c ä´MßWww·ªªªJ²Žâß%ï¡þ.僜¶OœQ_´`Á=øàƒ’$ß÷UWW§Ï~ö³Z³fÍ)Ó/[¶L½½½zúé§‹Ï}ðƒÔܹsµiÓ&A iÓ¦éÎ;ïÔßþíßJ’º»»USS£GyDõW5ìí×J’Þ|óMýó?ÿ³-Zä,þúûûÕßß_üwww·$)¯\¨¸%”ŠQFèøndÍ{¶ó¬Éú^h%.C.ßÚ>×ë;î¿3SŽ™¼NÜ=?ï¡|Ðêµ>ÙמžžϧÓi¥ÓéS¦Ïf³Ú¹s§Ö®][|.‘H¨©©IÛ·otÛ·oWkkë€çš››õä“OJ’~ûÛߪ³³SMMMÅöêêj544hûöí€!źüüç?¯|PÇŽÓ?øÁßBÓÖÖ¦/~ñ‹§<ÿ=3ÈÔsÖ1ÖŠ‘)Õ¼gƒñÞÿRcÿ „Þxã UWW—dÙ©TJµµµúIgø¿K'NT]]Ý€çÖ¯_¯/|á §L{äÈ ÕÔÔ x¾¦¦F¯¾úê ËïììtúÎÎÎbûÉ熚épÍš5úÚ×¾æœæ•W^Ñ¥—^*Iºë®»tóÍ7ëw¿û�¾øÅ/jùòåzúé§åyƒû_»ví€o+o½õ–fΜ©}ûö•ìÃ<ôôô¨®®Nû÷ï/Ù°ÆxÀ~8�ýðöŠ쇺»»5cÆ Mž<¹dëÈd2úío«l6zYAœò÷p°³Ÿ"UÞyç�ºé¦›œÓ\tÑEÅÿŸ2eЦL™¢÷½ï}ºì²ËTWW§Ÿþô§jlltÞ¡N}WWWÇú vRUUûA쇓Øï`_œÀ~8!‘(íý—™LF™L¦¤ëx¯)S¦(™Lª««kÀó]]]ª­­tžÚÚZçô'ÿÛÕÕ¥ .¸`À4sçÎÉîÇR¤î>ÿüóu饗:©TjÐy}ÿĵï¾ÆØR©”æÍ›§ŽŽŽâs¾ï«££cÈ“*���¦—¤gŸ}¶8ý¬Y³T[[;`šžžýìg?r™8}‘:xº~ö³Ÿéç?ÿ¹®¹æ�{î¹úÍo~£{ï½W_|1o*†¡µµU+V¬ÐüùóµpáBµ··«··W+W®”$-_¾\Ó§OW[[›$é¶ÛnÓâÅ‹uÿý÷ëúë¯×c�=¦_|Q›7o–$yž§Ûo¿]ÿ÷¯Ù³gkÖ¬Yº÷Þ{5mÚ4µ´´ŒÙvFE, ÀÊÊJ}ï{ßÓúõëÕÛÛ« .¸@×]w�î¹çž3º¾!�Nkýúõ±¿&‚ýpûáöÃ;Ø'°Nˆú~X¶l™>¬uëÖ©³³SsçÎÕÖ­[‹7qìÛ·oÀð÷¢E‹ôè£�êž{îÑÝwß­Ù³gëÉ'ŸÔW\Qœæ¿ý·ÿ¦ÞÞ^ÝrË-zë­·tÍ5×hëÖ­£>ÄEäÄL¤®€� f(b† f(GÈ 7Ü 3f(“Éè‚ .Ч?ýi>®˜Ž•S###Êd2joo×ðð°.\¸ +W®èÔ©S¦£å\*•RMM�êêêLGÉ©k×®©©©IgΜÑãÇ�UVV¦={öèíÛ·¦£åT2™TYY™._¾l:Š1ýýý ƒÐÝ»wõùógíÞ½[ÉdÒt´œZ¹r¥Î�;§¡¡!=zôH;wîÔþýû5<ŸÊËË …~›ò'ý¿óÁ.—KåååêééÉ–�L&£žžÕ××N‡\sG ‡Ãêëë£üýM&“ù-~ðk£Î€ÁÁA=|øPÛ·o×Ò¥K5>>®Ó§O«´´ô—¿û÷#âñ¸ªªªTRR¢óçÏ+‘Hdß[±b…Ád¹‹Å499©X,¦t:­h4*IZ»v­ §›=MMM Ú¸q£*++uñâE%“I:tÈt´œúôé“ÆÆÆ²¯_½z¥h4*·Û-¯×k0YîƒAuvv*‰¨°°0;tÉ’%Z¸p¡át¹sòäIíÛ·O^¯W?~Tgg§úúútçÎÓÑ`;ÿÛÓ§OŸÏç¸Ýn'??ßYµj•S[[ëLLL˜Ž–S¡PÈ‘ôÍÍ6�@à›ãÐÛÛk:Ú¬kmmu¼^¯ãr¹œÊÊJg``Àt¤œëííýæ÷LGË™»„B!ÓÑrêðáÃNII‰ãr¹�ÇãìÚµËéîî6 p˜`™ßg‚þ €e(€–¡X†` €e(€–¡X†` €e(€–¡X†` €e(€–¡X†` €e(€–¡X†` €e(€–¡X†` €e(€–¡X†` €e(€–ùCéRᆲe6IEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_images/histogram_design.png000066400000000000000000000422071516627434300257240ustar00rootroot00000000000000‰PNG  IHDR+^¾0KgAMA±� üabKGDÿÿ«1Í pHYs––qFþðtIMEã 5„Ú3ØC‡IDATxÚí½Á‹+[»Ÿ·úpÈj÷ lî7Ò>&|³>§zr�8wâ&œj�;Šá$¥ÁÙƒ˜4GßÙ�±„<ðÆAòà´d’9�ªÉ�„ĨîžØ`¶0†{zÔ]!Ü î¨3ø�õ­’T’JRI]ê~{·TZµj•ÖZ¿z×û¾ëìùÙÔ–¯h@¬ V±€X@¬b Þœ?ôzç¾OKTÑ–žwþ0�?xb ’Éu00ƘÇ÷iJkìÏãû,3&IŒ‰ãªË×4/¼]¡ÒíÒU –áðüÁcâØ˜Ë˪Ê=#ƒ-¼-¡EÆDÑãûê&S•ëûÆ„¡þšÍŒ�ßgÙùC>¾�ߦl·+[M¢�†1“‰1Íf•Ë?罞1žgL·+�âûÆD‘DK<¾o6�­¾oL¯'Ñ4÷Üž§vîv«jø¬Àb00f8¬V¨„¡1¾ÿø¾Ý–߆ü`dU‚âïDQuç_UV¸×qQ;t»Æ Uø¯ VàM IÝ÷�é÷«-9 �Y%~–}b4y7Õ\Óê²ß÷ûÆ4›ÆT³³½`�Õ.�b �ެ*²~T‡çéU4a§©–‚ÅM•Bi�`H’ê¯wú}c:-¿!VV"«ŠçUoU1Ƙ,3&ŠVçkq>š´7[Î�ó‡ X—¦|Y«—aÎ|_ç©>7ŠKkûYW+ðètltNõe[çÕÉDIÑ:�¼ÈЄmÌùCÈáÕóŒ Ãó‡8ÖkÞçDκÖÏ$t̼hÙT–DHŸ?�5e6ÛF´Õ@¬XeWÜh­©çµ¦A å¤Õä—mÜw–B´Åç)[ãÖ´ÑXU~Q™ªÏv ¶ì9¶¥¨‹mÜš"à5âûòW9ä$íû«òˆ¸˜uKù1z}.…¯›×Ï;.ï˪úÒ§4Mwͦ{t±¢‰3 �I-S,~ÞéÈùG�.‡ÜùÉ»5íõZÓÑȘFCŸÎqGžÏιt>l«5�¢Öt2Ñû圞ZÓù02•›Jóeêø(2æË—²ÎÄ›Îá®{0ÐgOOvM×úôä˜}Õõ»6Ž"+uîÉ„A  Þ(µ×ÓË…‚v‚y båž«ÒÊ[ñ‘+Y6/6 ©üïyóÇo[–1Zš ÃåpèNç°–9ï’ îëÃÞ<=½Ï7¸ïÓíÞ],;;ÙIôîÂ…gµ¦YfL·¦>Ü]d™&è(2F3·¦“‰BŽ“¤5Ux–|cšÍÖTâÅ¢ó‡šÄˈˆNGõ¾¾–#n’Hp�FòüΗicÖ»]Ýð^¯ÌRW™s諾——vì×y1eÛa{W‡¯]±ù ÔZ â¸5½¼œOg=¶³lù)߯«™¼¶1}m�Ƈ„æ0²íUéöç0FJ}2ÑÎb;oR¶Y¦%ª$ÑßÅKrP6m”7ïTYåFy§��» ”w‚ÕÿË�o�c[ Ç®‘Cæž)ªãùÃnß}ËJÞœUô£ZÕ­â�ñ}k�ø ‚Ãì÷`­>�Ž–xF¶Û9$ôÚmY£Ê×G©fSíe­Yq¬¥%¨'/¹QÞ©·ÔWDÖÆ²²Me“Dçl¶ÞJ0›Ù}ZScôôÐíÞ]fÃ9ËZcªöïØýV¤9ŸžÍç’ÈË'ë“ÏL¯×š&É¡ÚöÁn”7? m”W&dÖŽ3iº.ZæüÁóä'£ÉñX�³Ñ0&Ë6=©Ÿ?ø¾Kñnó�ÌϽ¿»Å`yÞ€Sæ72t&ÊÖÔó$P²l•ârQ56’¨ÛÕëúzÛ v»�byr¯eB¬q µ�LŽËU­n÷ù§.· õÚ2ø¼ªÞ(OŸ9ËC§³˜ŸCÑF“‰6³Ÿ+Ê1}TfÓ={Üùƒ¢8u ËQœ:n>"TÇî:6ižÙ%c*ŠÝ–�^@¬ØM£áDC鉿ÛuÿÏO̲¸¸ï÷z­iÚI\¯Uë³E ³¯‰t]YU¬>‡m'n®¯µ4Vv_£"Ç;ÏÃÉ ž¸L¨Y¦ñKéΞžòûÎ<¾O’Ç÷××÷¬sg³™wœtî]_ËÉ2I¬Ó¦„-k8”fšêAƦcwþ0:w“$*§ß/zxÒ9£È˜v[çÔ˜eŒçéÚäü©ò¢HޤI"gÑáPÑ.»,é° T?V%ÖQé2�œCó»N*ׇ¢wÔî.f³Ö´ß—5!ŠZÓáP4³™Â³Ì˜Ñ¨5�]ØÖlæ¢|’Dç°ñàÎ\(‡Ñëëy …]&ét\§³ŸG‘êlÔl9q,«EšÚ02Õ«8ŒL!ÚöéB9NŒ�‹Â³‹Ù|Ž»‹ñXçC j/'¾ä" L’èûó×ä•“DeYg[ùɰÛ4ÀñÐäe2œZñpþ0«¿Ûh“FCa®vO™2ôzÅIdžCYM$`æ'ÏsË4ggóß³‘Œö�RKúùåë <NÅ©hµ‡¬Ì‹BMN™Šzä—óØ^Dž•Ë6R=²„X Çòˆ�ŠÉû¯ØH"cÚíʼnUå�Fv‰¨XH)]ùZ¬*g´Ë0å£nvi“cœÃóœ_Ì|›Ð�Ž%RlbFÏ3æÃ‡]—)ÜFyƨõc-ñ¤éb˜«Ž�c¥r_g´ä3*×¾W\Öòwó‘Œ6]¼]žÒò�,5öøÁ@bM¹¤´´4éáqñÁ©×“õf}–ëÇ:Ç¢À‚—ûýïvO^ÌÁV"aµJ.~ÊWÖբϜŦ8·Àâw¶‡Ç=‡%XR^R¤XkÆæ…ó‡0,²ÀÈêÐnk’÷ýÍ‘�eüë¶ yÖÒMÊškŒ-f·K9Úq×Z•æç€M¡ø�¼¶8þú´.p<ÖR‡³̳.@�DJ¿_Þ¢b}F–y|?oÊ]qþÐëÉ*Qf|,?‘8§Û¼U§H¹(NÕU>ŠóÌE„V—E‹qýØmì“+²ž(E°ÒËÛ n4ìþ$7€ã Ï+Õˆ'V¬U¢øÕÆÔ2Ðá}<Štë«aŒ›„!R,Õl”§¨"í-¦Mìl8ôl–w`U¸qÚsh[œ¼Êmº§:Gqªa®n£‘"Ÿ\DèniáË/éu»²-·‡SjÓÉäüá{é¼A಩âw¸—ÏϼxñâÅk›×»_‚àÝ/ÏÏîõùó»_žžÜ߃Á»_�êÎçûùÿ¿û¥Óy÷K¯—u=W×CeÁ»_t:Ú§i~ï¨ù2–÷œS™“‰QTTßbìf—;zÖŸyñâÅk³ÏȼOʪ×`@{­kGϳþ-Uúô¬?§õ/)çQÆge¾Üyÿ‹w¿|ùRìƒ3™,ÖáÝ/q,ÿ¦(šo£ççw¿„¡{¯ÓÑ÷]›éü_¾×m]¹ƒÁ¼Tµ�ʘo×6Eßßì_¤¶�v¹�ø¬üŠöÊ©ÂTh÷Zõtæv€SàÝ;c¾ùƘÿï]®ÝWKOî«òŒÀ¢•å%¢8m²9©thÆcY\h¸,nEû1ÉJ’ßÞ@áåvÉ$_ß2{0•)wÑÈÖ©Lþ“Á@a‹ç˲M¡ájµÃ.­ŠX™û!”IE½‰MëwUœŽKñ^:p É&›;–X%ŽÂÐMÌë6aܼ$¢M*õýù=˜ÖÍ9«ÊÝMXë¼�Æb*Q_™vµõÝí Vr‘õB£ß7æ»ïÎÎ~þùùùóçÕÇ•u:¦¹ênÎþÙ<®«{]°Ö§×ée_ndøñGóøÃ§sÏ^+UY\áe�«P´~0‹“÷l¦1QdÌphP÷µ¾•Ûƒé�ìQ²Bîæ€ŽXù•M›úµ¦OOÆóüüùsó)íüAu7�Ÿ?ŸÂÓ¡3U¾Ý'Ùß§)?‘{özïË;¯G¬DÑùCTÑŸÜFŒIR¼c7nô}‰Ýý«•߃é�ìÞÔ^a(£Àn ¯ ”ÙÌFîì�µhO¾î|QdÌæ}‰Ê�o•Ï‹„ˡŋDÙl¶*²J[¬Â~¶»hC¬À ß7& ·Ë ²l!pVŽfs}âºáÐn9p˜ëY܃éVÀv[m¸2m·QX…–ÄöY c^=Úw¨Ó‘•bÙ"â6ï³bFùFæ�ò<ùЏå˜Mb¥hÿ w.»?R«N¾ï¬�ï›Íò{0mWî|î–^Oû4�Ç‹e¨ÖYÙæÌ±~3vï¦âå5Y|Šö¯Úbÿ˽®?ÇñõçççëÏûîU¡| ÇÍmÇeãàë‘‹áømT¿6°9Nãž½ÞûpZ}‡×¦ûEÊ—Síž7«sÊl¿ή{0½Lî¡2¹U⸊vÀ²oȺ†«¬+û å¥FÃ- ié£Úú×ÇÙ¾ÌÞMjë ¬*,@•Ü^Ù( kv^tÆKS™ŽõïÍ=Ñ6plÚmc&“ó‡¢äfû`—˜ÎÎ$\<ï-ï¥6èõŒi·«HšˆX€=Š&e+ÚPNû]+\Æã›ûÃlf0o˜Íκ]ù]4›ÕeNù¹�úÁª$po›í¶šD|ˆØQ¤xž{š\´õZ 6v0Ï¿:�Û+eéÄÚ‡,ásFu)é÷+3IÎÒÔß?ìÆŒõGŽÌÆTÙˆØA¨�žœì2O¿/q²~M]â%MíSç핵ÈXÑ·Wí6V8¼`i·µò`�ßCg¿2_b •(’¥µÚ ääY€-…J¯§- �ËË›ûnw“P)/ãñÍ}·kÌå¥Êò}c&�àð‚Ř,[ŸÐ Ê •FC–Ò*—×–()R|_ÖësÒïKhì�,)——)²²Èzƒ•-XÞö’Mµm9›UùS–(!T¢È˜ÉDBÅYSª>�Êl6•õÓZY¢ˆ;ð¶A¬À¡ÒhÈÑùš4›‡´vh9éòÒESôzª¼UX€ JëmýK�"‚º]‰”0TªuØ€ÓË ¬äöJ:Ú¤¬šˆ‰í�¤¢„C¤±o\¨Ø<Æs}ýùOtN+’:–ƒ+9ýÛïï–\�`�ݳ¶N€X€7�Bˆ}_Q9uHÎrbà÷BŦїH¨CúûÅå îbÞ4v3ÂÍéó�+XÆc¥2÷<%ŒÄ ¼Ql†Ú:îqbó» V+ð†q–•úÕÍn7�X@¬À›D¡Á¾oL–ÕqOÕ)ËŒñ}ùÖbÞÖba-uÄZ|Ø)±oë¯Rç�ŽmÝH€X€7ˆ³YýÅ V+ˆxU€X€7Ì)$]³NÀ/¿ VàèœBÒµ:'­Ä ”º']«{Ò:@¬ÀQ¨sÒµSHZˆ80Öba}CêÄ)$­Ä kµÃ:-©.ÖÚƒ¿ bÞ,7÷³™Ä€ç3Ô§fƒ�ê”$ª# Và ÓnËÑ6 o¯^ÞwEuCÕ©Ýæþ Và�#ËE¿¯¿ƒ—\Ò¹­…§ßǪ€XøU°ôûõXÊ/ÿXˆcÌK/±üˆXËòrP£q<¡âû΢Ón³ü€XX!XòËA“ÉíU§sx¡Òé3™ãyæÝŸÿ9yU+¸¾VþÏ3¦×»½ŠãCXYn¯�Û«86¦×Ó;ýþ×ÿéßû{ÆÄñíU¯WÇ-±5àæ>Ën%Z²Li﫵²8kJ3›ÓlÞÜw»ÿÃ?úÿAKQ�Ž1_¾H´o9 +pR¢e<6æÃ‡*­,óÖÏSÙ——7÷.KíÍýp¨¥ Ïs¢e0¨C8<_Ó°�`É2c®¯%YB¾|¹½JSù¶è_W$NÃnÑ>ˆ€E~)áò°¤éÍ}·ëö z=‚EùdœÃò[æöÊón¯‚@/Ú±p‚¥ÝÎ;æjù'/Xì2ÑéNΊp"�Œ°á[Ä ÀÉ ˜¼`étn¯ì&ˆ§ˆ)ˆÝÛÙL´ý�¨Y*D¬ÔH°DÑé ß×RW£Á²GuÈbÅV §Ñ@¿Òšözù„VK¼ûö[ódŒ1ý~kúô´º¤n÷îb³ú?Ègâ<4ß~«ûýó‡uu¯Çm£ºñÍ7§xÏjÁÿlÌwßý›ó_Ýþö·ÿÉ8Šþû?ûã?þ_ÿ»÷ïöë;ÇœPídš$6K¯`°Xª+'OQfÍ¿Ÿ.¾ûn}9ew…]s®ƒ±©îuã%Ú¨nœÚ=«ñÆüòÞ˜þĘÿì¿ùÍoþëþ›ßüïÿ÷)M¨ã±2öf™þÞ,Vd�ñ厓€éõô€�$7÷ê{òïˆ"Ûï/ÛïË�Ä÷�%Hþ%nGîÙLï�Ç6Î6盿� öx½×ëéöš‚@[78Ñ"ñEú,Ët¬1ù:¹6Š"µ‘Ý]Ü[�Åúé[Y¦v]·¯`Y©ˆMK7véçùùó绋ý­c |H¿.#<~þ|Ìóî^ßã·QýÚà×'»¹gµåS’Ü^ýëmLÿÍï¿ûî¿5ú§ y.÷|ì%8=ýÛ‰Ô'‚ È" Ix4Ò.ÕÃô¤?iŸ$ývÊ×lZ1`ÑD>Þ^¹ÇZ}ßëyya¤Í,ŸžTöpèÎeŒÎ5ßÜ[ëG¹ó­o¯NGms}}s?›“$öº´;¸-W[;è|6Ü}±¬(’踼t¿�$Qù` 4[¿ÉD‚¥Û•Xéõð…9,8ØÀ«ÆNŒšT|ߘ8¶¦ûú¡% W÷ñXõ^åk1È:°ø°•·lsܶAÞ2‘�óç²ïÂYØ÷ç…B’ìæ˜Üë©�…ìp(gíü²t–I¤¥©"•ÎÎnî­¥°¬ì(Xn¯šMMÚV°”·°� ÐîÑù‰1IŒ ÃÛ«0Ì/3h2n4-.:fûãvc•Ð9N»J ؽ¨ìR¾m»ò‚TííyjëÅsÌf² …áüçX<±¬ÌÂb},êeaY^ZœÎËÖ½®V¤ªÚ­ÓѲŒõGÙED”qæg™±pÁ’eó‚e2©O“(’S©|HÜË:ĆἸ*k½x½Q*r®ítä‡Ô﯋:Zý}cÊ%›#Ú±ð"‚¥Ñ�…¥‚¥Ñ�“hã±,$ÎwEól¶ê‰_‘-å�[=¡×2³ÝùÝ´:¼Xû ­þ¾=W’ä£æË°¿ –}+/&X<屢"QÖ=¹¯Z j·eq™¯»¢dòå•=®ÈÂpÈDj‡8_¾�‹ï÷û·WV %ÉíÕh¤è˜nWoÉÁ4 ÝD>ßÜÛ w(¾/4›é¼Q´Î'äö* U—áPËc�Ž"¦ºÝõ×EúžïkO&¿\¾d³™¾“e¶ìÍ×Òé¨|íª­6ì÷‰B¬¼aÁÇ·WÍæ¦ÉÐZfn¯âxõûžgŒç¹I?M�yzº½JÓ›ûñXB"ŽN\nò.'TâØ˜ü5$‰ ‹ÇG‘„Íå¥[ÎJ’Û«^ïöj0¸¹o·W_ëphÌpx{õü¼ºFA ºØ²ÇãÛ«ÑÈ–½Y¨DÑÍý‡î½,Ó=úðáÐ!ØP ,T$XÜ’�çi2¬"Ä7òV7¹2|x00&I–ÅÖ*ñÕëÉš³8ñ‡ ñÞwé%Ë–Ëî÷Uöê((-+u:ÆŒÇó÷ÊFÕ=\+,ã±]šÙ¿ÔÕÉÐ�–ï—=¯„ˆç…»DnÕ;;!µNÙºe™ó¥±~+³™Kru‡e €ÊËõõíUíë7ò2l;�—±š2¬z]}×YŸºÝc A@¬ÔL´¼œP¹½êõv÷_ÙÖ‡£Œ“ê!ýBÖ•�$ÖIØ-ýÀ)Â2À«cÙÚp{Õh” ÖòÊ6K$Iâ²ù.žÓZ6Ö …]½˔-�b÷•ᖧʶ V`gëB~©eÞÚ <)_¾3™”+¯ÛU(ôâòͲ Ѳ—=~q²—s뼕©È³É§ÅóÊ•½êZ¢hñZ%¤e íÛŽ Ë@/„Ë=¢'ÿÛ«8¾¹o6‹ÞפëûnbW^•›ûfS‘1q¬ Øó–A–éåyúΦ�êñøöÊcƒÛ«ñX“z^¨ ·W³™Î­%/…·WÖš†6Ì|éùºÚü1V\E‘»¦|ÝÛmåmIS»‰d¾ìUíusŸ¦®n£‘®%Mu-³™k‡íÚŽÏÙºÈvp´¦qlcýï.v_û<P‹?¾?;;VÝÏ\Ýß×Ýö%Ú¨~mÐëéÉñ4îÙë½§Õwl”‹‹Â)ú<˶™Œ%<ïæ>IdÝð}åt)ö±Ç¯;¦è¸Muw�FcSÙe®¥ªö,+°›�Hƒ`[ÇÛüÄ-�°þe'úÅãÊ8ÀJÈìÁ³¹nÛ·|VÞ8�Ò>ˆ¨9Q$ }N–�Þ7÷LÄ´Ïé‚e+ˆ@¬ Vr ð qÙf�yÍYY]&\cvÍl õË À«Äó´UÀh¤}sæ'øÛ«8¾½Š¢Cˆ‡C•]Œï+GJ¯Ú]+PCnîíFE• p¯ªY]ö!ÌÍýpxs}ÍG¬À«2ý¾1ͦv3>NÙZ–j4h}Ø|VÞ¤`9Ü.ÒÅe‡!­»‚e ¬ÚH¯èýü{r0­îœeÊ.sÎùr c:�2ß¹½ ‚Û«rËS*ÞXV^L ø¾œ_ƒÀ˜$Ñò‰õíˆ"9ŒöûÆt»óÇöû·WiªÏ“äöj42Æú¨lβeß^YÁ1›Ý^õzÆŒÇ7÷Ãáú²ƒ@×âyÆ„¡:‹ßíttÎáÐÏÓyûýÅ(&}¿Ó1f6Sý¢¨Ø/+°7šˆ›ÍÛ«8ž84f8¼½z~.>ÖóŒñ<+N$.žžn¯Òôæ~<ÞöœåÊö ”,[ea¹½R~ù©¸e¡rmØhlÛŽ«¾Sôþ.åÃn`Yù•Ö´×[kŽ|÷í·æÉcúýÖôéiuIÝîÝÅfg¯ó‡yïÿÃòí·ú·ß?XW÷zqÜ6ªß|sŠ÷ìõaûΡ±¡Ây^[µ,›Ï`›·”t»Æôz·WÃáͽó™Q”OÞÚÒí3Ü^õûùãŠSû7Æ|ù"�㢌6 •¢ï½¿Kù°;g.0î­‹•8®fŸŒfóîbµ)óü�8=šÍÇ÷‡sìÔÓy+"Æ —,SôK–é=MèNÔØ÷|_Â&4qÎf7÷ùÜ)‹ÇSüþ6eÛ\*¾¯c“DyX–ÏysosÇØ°ã~_Ö‘ù0kYPlTPšÚsæ#“ÜqQ¤ãf37nÛÜ+¶Žž'1¡ûWf×i}g2QÝÜ~CEï¯:++ùmÆ‹è÷�ù³Ÿ~~þüyÝÒÝÅêµU'VÔ��ƒênÎþÙ<®«{]°•c¶QÝÌJ?þhøátîÙkå×¾s`±â&E;¥éͽ�Ž™Íæ­§Í¦k²m°i;€üqNùPkw¾,+#V Æå¼ûåùùÝ/ÏÏǬû»_âXçݯîÇ«ïñÛ¨~mÐë�Ò={½÷á´ú¯å×?þ»½ípú/làU‚óë뱯”(ÒRœ:DÀ«äæ¡òZÀ²ˆÄ V+ˆ@¬ V±€X@¬b±ˆÄ b+ˆ@¬ V+€X@¬b…&Ä b+ˆÄ V+€X@¬ V±€XÄ b±ˆÄ V+ˆ@¬ V±€X@¬b±€X€Óàkš@´¦QdL£±úˆo¾1ÆóîÇ[Ó X}Üpxw1›m:ßùC¯w¼«ûµîç?þxþ°®îõâ¸mT7~÷»S¼g¯�_û¼(gÏÏ4‚ÄJSŤÐlÞ]$Éê ˜8=šÍÇ÷«û5,+¿§Û5ÆóVÞïóÝwgg?ÿüüüùóêãÒ´ìàw¼kSÝÍùÏ?›Çuu¯ q|ü6ªç?þhøátîÙkå×¾ˆ•:pw±^d´¦OOÆóüüùó:ËIYŽù”vþ º›ÇÏŸOáéðüáømT¿6øÕÊw"÷ìõÞ‡_û¼(8Øb±ˆÄ b+ˆ@¬ V+€X@¬b±€XÄ b+ˆÄ V+€X¡ ±€XÄ b±ˆÑšú~k†45ÔR¬EÆŒF­i§Cs@ ÅJ£¡“„怊ß7Ƙ»‹4¥¹ †bÅóhf¨¥XiM­PÁª5+Ϊ‚uj(Vî.f3ýÏ:ÙÔH¬Eµ¦A@s@ ÅŠµ®°µ+Y¦ P3±b“Á!V –b%Me] üV vbåî"ËŒõû@ÍÄŠè÷�u…0f¨™XÁºµ+Š•(ºeùúX'º»˜ÍZÓáИ(2f42æò’æX�öX[M9›¹Láu®w–Ý]°G÷ew¾:îéº]%‰óýÖ´×ãg° Ï3&ô ÇÆ ú[ﵦqÜšŽF­iÖ³Þ£‘1¯g¼oM}_mE§Y—Ó¼/G+ò]¹¾Ö_�Ný:@½¸»˜Íî.º]·”®¿Ý«ÙTÃhÔšõ«÷k³¨X¡X‡TëëR$bNõ¾|uüpšÊÂbŒ1ƒAkJ²8€ýÇU-³×á‰ÿu·u¿oL³iL»]çºh¹çõø‡~õr <Ë5¨Q`wìÖ&0~K­Ô¹.¯kåâ«—;u»-3”ïÇ€}°chñ$Úšz^kº9“xÞÚí¾3o_5^o;Ž·¦�FQù«ÊU}sŽmY¬Çbû3êu¹.�ƶiBê¾ÊñõK�øî"ËZÓfScV°4›uQ«§‚&«04&Id¹^ü¼ÓÑ8;ãy­éhdL¿Ÿ�QÐC£aÌx¬ã³LijÇg9d�ö|k6õ½(’ó¯ïËwÆ.óoªs¯§òg3% íõŒévm�ËmM­ëtZÓvûîb<Þ÷î»dâûú|8ÔäÇnËÛ®Em`…A’¨}{=ÕýÝ»õõ‹"gñ<ç—4ÿžê3™81:«^Eu±ÎØúM82ß]ØòóuC]w’èwa}Z+kËhdŠ-(óÖ‘ ÈO²ÅB%Šî.>|pïe™ȇ‡­0Ðß,“‰B[“D’ijL³ÙšÆñü8>3¶¦ÏÏåÅU§£z__+ô:I$¸F#cTOWîdb¯OµUûŸCçÑÄ,çd‰5½Ÿ¦­išJÔÙ y�Yl‰� �£³%IÊì…'2óôdL»mïŸ{¯Ù´ç¾»¸¼T[äÅVÑýH�_Bk½ðh4Œñ<×ijÌÓSkš¦eÄà1ùê¥+ ŽÑlºtü„4lG£Qݡɹӱðü„fŒŠ}’OÓyËvšÚÏ“‹Ã÷çEG’Óh,/Gd™&Ô4U$ËÙY‘õh¿sswÑnë\Ѝ’€[*ÅâQe. ¨r“½‡ùˆ½§9qþè$©ú^ä®»ÿõ[úº•�²¿¾–…¥ÓiM“dó¤ZôÃ\§Ö¿ùÆcÌ»\¯˜‡Ã2IšÎŽ)Ê~­ûù�?ž?œÎÎ×Çm£ºñ»ß�â={�" nÈÚ1ÿïy²”\^Î�?A É4Ë–Ç­ÙlÞÏ¥èZ³,/±!Á!á`sØÉ‘íçƒíÏaŒRkL&zh^lçUØe¢/_ZÓ$ÑßÅËq«I›4J †0Qd—Õô^Õ¢±ž k+V¬ÒoMû}=ŒFÖl‘³BÝ]ôûnž²B#I$®ò>Fvy(M÷,§�䫺UHë­ ¯c  v9§ÑpÂÁîpßíív¯§û½ÙL–€0´¹Bô*òc)z°«"YÞê!?›êÛiõ9l{8qs}]~»¢¬Áž·�À�h”S­}xvïEÑöÔ‹~H§�ä¬|°Ùñ�â�c5î±}Wê4ú¾–Åí“z–I0̇¥jRít$N†Cc:’†î‰=M5iÏf.TÖóŒQ¨²�¾qKJI¢ð_»”"g^W–­Ó``ëvwÑlºzÛr´ÕŠB‚£HŸõû®>ÖïÆó\nëKh¯¹8WHq»m>‡\¬¯H£¡÷Ö¬ïÛ½–ä4ë|Kò×Ôïëû¶Ž.ŠgÛeçØë¬4V,-–UÔ¾ùði—'¦ß×õ*yÕ}Ñß¶½ÕþóáØˆ•7-ŽíÚá[v*(?n6ÎÊ1.>èÙ¨˜ù¥+>Úíŧw•7i‰¨h²´“ �6šÍÊDCÎ?˜î»Üó²çÐr›róm²KYªkÞâ³üÞö׿Ý}Á²²£uåîb}@Øu¬U˪§hYe¢¨NOÙðöøª®“ NSekdgf€Ã °ØÕN²‡Èï°_×»zv=´8;#ìû`8›))§�:±c­�rIÓíœTÏWõ®ÞñSÿ~ £ècÇÃܳ5�ãŽMkÚëé¼§a¡z‰6ª_DÑ)ݳ×{Ûw4Ö¼|¦æ»‹$¹»¸¾vé#?•v¡uà,+Vá‹FÃ¥¨>/‘:Ý&Ú;•8{ÒËþwõè;õú­Ë‰“\PP?jnYq¹¸Uˆ•ÚáÂè0… Vj Iá+5Ä9µ ðV©¹ƒ-Î…ð¶qâ£$™<¼íÞ–QäÒê—KÛˆ•Øêy.”Õ¥3vûGèÿtbx}xž¢†¢Hc™dw|q¬Ðì$1&Ž[Ó,ÓF�€XÙ»ÇE‘ƒ­ï/¾¯­Ð‡ÃuÛ‚—§ßÿ>=Uw-Ýî§±SGÍ#òîÛoÍ“®©5­òš=À¼å\+ß|c‡§tÏ^¿ï;âcxüߤötévë’s§5�¢Ó³JhW介áPûÙ é€XÙ»CØì‰ã±~X³YÞªÏó¹(¬xét´ú¾¢å»ïª:ÊsÄü O‡º¦CC¾•Ó»g¯Œ'~“ËŠSL%awn¶âëb¥2²Ì˜wïV‰ ýàf³|#m~¨M¹ôo§#ÑÒín/ZšÍOãõÉ‘>†£‘ÛÜn—ž¦y Êê§¥³³ãZ(‚À˜fó�»›VW_m¯yÌ6ª_ôzú ŸÆ={½÷Áöñi¼þ7ù1ìtŒñ¼Oãr;Å 뺕ì*¬µà¸Ôx#Ã,ÛV`(et³)ÕlÍ”QdÌ—/v›ðji·íf‹:Ï``Ìdò1|~¶i´õÂBð6°o�-ª‚@¯ÍA ùå¤ÖÔóô½ÍKL²fw:›�STör}ZÓF£ü9Ý÷×ï®Cm¯ºV߆›¯ms}ÙB㕉•ý„Žö´0æÃY^ä\¥§Õêø4Î2c®¯‹óÀ8ëyb^?CÏ“åÁ÷?†ÇY*iMÃPÖ;Iv:«Æ9ís4Óh´¦£Qk:X_9Ÿ®ŸÈ�éõt�S/}_{YM&²ŠÚ:ØEg�ÑûVÌAѾK­©ïë}[Vê{ž§zÏ__kª¥;ÎêóÉ$_–u(ÚwlSnº¶²õmM;ÕµÑÈ·”ãë×|qvmÒ™Ô;uŒëëýp­`™Í>†í¶1£Qñý~™e!8uòK$ahÌa7Ôd×ésy鯳$‘( ôÀ–?6ŠŒùðAÇŽÇ­éd"?À$iM×ûph)2I4©§éÝÅü2—n‡C•éûÆt»½žõqÑDµ„/KÄh¤K[–"žœ€ð<{>í ýôÔš¦éÝÅx¬ñ<dQ·$‰µ°Ì—eÌüqåÚpÓµ)¢hS}=ϘáÐù\cÌhÔšŽÇUÍEXV^…hévµ4”eê,q\Æ\Z^°ŒÇó»•Zþú¯Ù à­�_þ9†oG¯gLÑd7*È`±>i:lšº(™ª¨²L“ušÞ]Ìfwggó»6ûþ¼ƒn’ÈÒ°ji$ò‘G®þ.­…¾¿hÉ*’¼Mnº¶uõ]lãÅëÄÊÜ“Á‡ê ¾_µ`‘Ú¶/ZþàŒ±¾+$¸x­hÙ'/P|ÿcx¸‰H“¨ç= Éj±(˜4Ñ‹‹ª)~@»»è÷5Á'‰–N‚À ¼Uãã☺Hšê¾|ÑÒJ¯×šÁ²ˆ¨¢ W_Ûæúb=9ªXÙÆ1ª|™óŽQeŽÙÅyÊ©Üfs^°Ts-ò_É›Fû}gîtäx‹£-Àë¤Èÿà�> eÆ’ü89ÊgÃ.ÉØDs‡]ªZË;-§ØPâý,ÏnLï÷%<:�ëƒAõm'!V69FÙ5>½ÿôä¯|_DZœ�òßétA#e½ø¹;¦Øyª:ÁR�ÉVËAJ6ôiÜí*|ñòRçj4Œ‰ã¿ùÿsö[~x¯Çªb£ ÃÃYTË,ÛäŸæg3MèQ¤1t0PJ‡ý’»m´0ŽßïW±ô$ñ%‘»‹ËK¥;èvu�›ÆõmÛ^’¯Ëý 6;F9‡¢Á@³Ö ï.ÒTNFý~>_„!wαJhã¸5•Ø&ç©}‹Ê™ùF£Ö´ß¿»°£\Ž„bæ¿+çÚËË�¡:êßø§Æü�:›}4A°)�KU,:•Õ�·œ_ŵÁ¾¿C8^ß Ãâe û¤¿ú>nÊÛ²š$q>xóãˆ{àË¿†Æ$Iõ9{¶c�κD�Î)v›:Úä¡ÎBtwÑï»ý”Öù®lÛ†p–•²ŽQò@Ï2k1‘…eQ¨ØN<ÿCrÇØ§”}�§Ö –»‹ëk7�È<¹ëS^œEÿYYðex V•uÖ…(:Dׄßí*Œx±|�­óV“Ù̘^Oaµ6ŸHl?ÞeÙü<°¯å!ÿàéy»•WdÕò¼MóÄöm/ÉÙ¶ù%Nìzgq†Mu€ÉDë¤Y¶èì$óÜh¤ÊâÄÞëé  Ûu›Où¾ÛË¡ú§]“òè…ªjcDkeqƒH»},+ R¼hDÝÝb²j|êõ\äHšj 'M5–Êj¢‰> µ©ë¼5Gc©B•�³mÞzP.�ƒêÇÎGĆG‘Îm}QÒtq²×1½ž¾›¦:v<–Øð<[ïU׫¿í9ló~ße·Î¶ÖB³ª¬ùÐëMm¸éÚ¶©ïÝ…M¥úÜÎiXQ++Z¶ C·W�ÅéÀ� ¹¼\œôÝÚe‘XÑn½±�ÆÞ\‰‰|�jû„”WéI²hªnPó}YŸì5É!WŽº€XÙå!r1<9¿ãp»½<7§·›0µdãÆéíêºËrÏêqÛ^ïº6Ø· áDÄŠËØg ÙÜ:±2H…Îoþ»××ë¶è¶&Ê|‡�hêõ6}w÷£¡:[µoEKš:u\t½SeBæ°² VŽ'dô°·ÊÿF–Qtj¾mð¶(é³ÒéÈš±Ú1*ïô*qÓíJ©§ébvWMöY¶*!Žóâ^Ì] ç)Y!“Ã@É~º]9÷ûάh#—”V¹5ýòŦ`Všå8¶kÂÛœ_8,ã±Â–W�)JG;Á++E;FIÅÏfNØ\_«£,:¡Ùð²y/û}÷ÎnÎSû‹–,s¢ÅÆñ‡n]Óz¡[‹H’èóíÍ¢rʽ¼$/ âLãð` <'Ö¹6Šd_NŸP7J.mvŒr ½Ñ�XQH²¾ks©Ì;É a£‚l¹³™s\ ‚uÎS/ÙpùµÒ*Á—àt©ã2Ð⸕ÈÚÅç ¶bÅýЫsŒš/WŽMËQEÕ9O�Þ —÷ei6ñc@¬ V †Ÿ’â}7%6b $_Ñ€X@¬b±àøúP+]¼MgŒÇ�Ço9¹™ÚÄ&dʲOãz¦¸VÈ´MÍ�¦‡¼g:—Mü§<:Æ$ IðêøÛ¥?Ÿb¦OÂkà«ÃüØ{=í”eŸÆÝ®ö¿™Íì¾7ÇêpÃ8þ¥ë) ˆ•?ö 0æúúÓØíߣ ¬Í¦1Að1œLª;ߪÁ+Ü«|Ïfèëýöi<~__~ôýOãvÛ=±‘ú»ž“ýù”û3}+…?ö(2&ÿcÏwî,³VñD&3ìüFˆî\ý¾Óv›Û\G|_›?æïÙa7§ú3Ð'á�‹ 4Q$‡ÕOŸÆZë6¦ÓùLå ÃõO>¬³П±ò{ìÌe6ܳƒ_±ÉW¥ýñ¨÷íF›ÝUŸ�^ëŽÉ×eóºíǰÑP™Õ¯ñ®ªgÑûÛÖ{S{•oÛoS}uìê6._·Í÷ êÕŸËôå*úsÙ߯6ýâ�}ù¿gú$¼f*Œ²¤Ìú­=Æ Z«C•Óïüý3V–©ó¥©Ö‡ÕAÜ`†n0�?�‡CýÝë©,»¶žï`a¨ï‡r ët>†Æ¸òóßï÷?†ijM¤ÃÑÈ»V½Øq{=­ñÎfZËïõŒév÷‰Xu-j¯(R½ú}cºÝÝëíûš(f3«'êÕuêtTîphŒç©ì~ÿÓ8Më¥ûh۸ݶ÷LŸ7åà¦ûe;ƨüápþI]ïé·aý+²Ìþ^ªíÏÛôå}ûó¦ßÆ®ýù}y»ß³ú‡^ã±ûl0Ðgó¿_ú$¼zžŸ«yýô}ÿôýóóOßÁæcƒ@Ç>?/6™üôý—/?}ïyËï�FËçìõÖ×)Žçß‹¢âò{½Ÿ¾ –¿?üô}¹÷ E†,.(Gçqš*jâìLN’‡¹_ú|<ÎG‹è½,[Ž )º^¨¾?×­/oÓ/Õ—·©³Þ[^.sß§OÂ[á«êª2�Ù%+[ºóJß=tQ�¨x^Ž‚y3¥¥\èÞ§q¿¯ÎŸ$Ê ‘³|‰õÜÍõÖÀëûåÃmûÉœŸ©ŒÅë,ãïPåýJM‚šddª�ó¶ÞcP|Éþür}¹\¿8T_Þ¾ÎvI'/8ìÒ }+;ble4í¸ËtŸ‰¿Ìà¸{DÃǰÓÑúkh°ßo`8<۶庉«Û�D«±mw¿Æcµ»,}_õ²¢Þ#³.ýù­õåíê¬É_“ý¼(È[4è“ð6¨ÐÁv8”9²Ì�Ù³KGZºø1ìõŠH—àÝÊ_?¸Ùœ>8m}XUs­Ibq�™ùf¹kø4β�áx¬'9ý¶Ü{A°h*‡—îÏëëûó)öå]ê<3É2±œ…> XV¶D�ºß׺îêµhuzy¥;³a™ĪðurÓ“Z’¯—æÃwíð�Ž¼Û‹ÈM!…» ªû-/i�¸ÈT¼êxÛ~ÅOsîUÅ.÷+IT¿ü:¾5EGƒãË÷çr}yS>ž¼}�eq˜Íl4Ï¢ ¡Obe§®ßד@¯W䈦§ƒÑHÁ: H£±<ë‡>Ö–eó&ÇõOR|º]u’2åïK¾Ck]¹Ú'š*"Û‹&óUƒq·+'ºùã:Ymjî]î—w ¦ÖaϽE8ñ»?—íËÛõçSìË»×Ùúx¬:'}^?gÏÏÕ*5ßéH=Ûn¸'–ÕèÃ8v\ùdS󹜒�c=jwÐOãñØåUpkéŸÆ‹¹ÂÐ=%„a>×BÑ÷ÕAín¤Öym6³åê ¡×S]ÒÔ™8mþ›és¹Ü2�UC« ›'"ËÍ“e®ŽÛÕÛµ‡}ÂQ^ 7ø¬:Þ>%Ùk�Í”3!ŸcÃÖ+Ÿ¢ès ~zÏ:üæÛvýýZn«^O›î¹ß™M¿^$C•ýy›¾¼k.óÛØ¶?—éËŸÆù¼FÛõçíÏ�†1“I~Yjõ=¢Obe‡A.ïEŸ$eÖT5À£AÃ&EJÓui¶­çû6ËJn³ƒb5i¼]ØÞaÖ�ë¼ëµ¯+[Qž§AjuÛä�?ΤYî~Ù'¾ü1EïÁaûó.}y×þüúòúc�2íAŸÄÊÑD7Àq{NYÜЗ ¾¢  ÎÔÆ²²jÝç+€Ó‚¾ ¯V¬Á2 V+€X@¬ V±Pÿ?>Gˆ\è™B0%tEXtdate:create2019-11-22T16:53:25+00:00‚œ…%tEXtdate:modify2019-11-22T16:53:25+00:00óÁª9tEXtpdf:VersionPDF-1.5S6ZÁIEND®B`‚scikit-hep-boost-histogram-5f6f3e3/docs/_src/000077500000000000000000000000001516627434300212055ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/000077500000000000000000000000001516627434300224525ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/.gitignore000066400000000000000000000000141516627434300244350ustar00rootroot00000000000000*.pdf *.png scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/BoostHistogramPythonSource.svg000066400000000000000000001340211516627434300305230ustar00rootroot00000000000000 image/svg+xml scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/Makefile000066400000000000000000000004011516627434300241050ustar00rootroot00000000000000SRC = $(wildcard *.tex) PDFs = $(patsubst %.tex, %.pdf, $(SRC)) PNGs = $(patsubst %.tex, %.png, $(SRC)) .PHONY: all all: $(PNGs) %.pdf: %.tex Makefile pdflatex $< @rm $*.aux $*.log %.png: %.pdf convert -density 150 $< -quality 95 -colorspace Gray $@ scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/axis_category.tex000066400000000000000000000017211516627434300260360ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[ xscale=4, every path/.style={thick}] \draw (0,0) -- (1,0); \foreach \i in {0, .2, ..., 1} { \draw (\i,0) -- (\i,.2); } \node at (.1, 0) [above] {2}; \node at (.3, 0) [above] {5}; \node at (.5, 0) [above] {8}; \node at (.7, 0) [above] {3}; \node at (.9, 0) [above] {7}; \node at (.5,0) [below] {\verb|bh.axis.IntCategory([2,5,8,3,7])|}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/axis_circular.tex000066400000000000000000000017251516627434300260310ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[ every path/.style={thick}] \draw (0,0) circle (.75); \foreach \i in {0, 45, ..., 360} { \draw (\i:.75) -- (\i:.95); } \node at (0:.95) [right] {$\pi/2$}; \node at (90:.95) [above] {0, $2\pi$}; \node at (180:.95) [left] {$\pi$}; \node at (270:.95) [below] {$3\pi/3$}; \node at (0,-1.3) [below] {\verb|bh.axis.Regular(8,0,2*np.pi, circular=True)|}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/axis_integer.tex000066400000000000000000000015661516627434300256650ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[ xscale=4, every path/.style={thick}] \draw (0,0) -- (1,0); \foreach \i in {0, .2, ..., 1} { \draw (\i,0) -- (\i,.2); } \foreach \i in {0,...,4} { \node at (\i/5 + .1, 0) [above] {\i}; } \node at (.5,0) [below] {\verb|bh.axis.Integer(0,5)|}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/axis_regular.tex000066400000000000000000000016161516627434300256650ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[ xscale=4, every path/.style={thick}] \draw (0,0) -- (1,0); \foreach \i in {0, .1, ..., 1.1} { \draw (\i,0) -- (\i,.2); } \node at (0,.2) [above] {0}; \node at (.5,.2) [above] {0.5}; \node at (1,.2) [above] {1}; \node at (.5,0) [below] {\verb|bh.axis.Regular(10,0,1)|}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/axis_variable.tex000066400000000000000000000016611516627434300260110ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[ xscale=4, every path/.style={thick}] \draw (0,0) -- (1,0); \foreach \i in {0, .3, .5, 1} { \draw (\i,0) -- (\i,.2); } \node at (0,.2) [above] {0}; \node at (.3,.2) [above] {0.3}; \node at (.5,.2) [above] {0.5}; \node at (1,.2) [above] {1}; \node at (.5,0) [below] {\verb|bh.axis.Variable([0,.3,.5,1])|}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/_src/images/histogram_design.tex000066400000000000000000000044161516627434300265270ustar00rootroot00000000000000\documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows, calc, decorations.pathreplacing} \usepackage{units} \usepackage[graphics, active, tightpage]{preview} \usepackage{pgfplots} \usepackage{amsmath} \usepackage[outline]{contour} \contourlength{1.2pt} \usepackage{ocr} \PreviewEnvironment{tikzpicture} \pgfplotsset{compat=1.16} \definecolor{presDark}{RGB}{39,1,136} \definecolor{presDark2}{RGB}{87,80,149} \definecolor{presLight}{RGB}{139,131,215} \definecolor{presLight2}{RGB}{0,129,203} \definecolor{presHighlight}{RGB}{215,50,50} \begin{document} \begin{tikzpicture}[every path/.style={thick}] % Main grid \draw [presDark] (-2,-2) rectangle (2,2); \foreach \i in {-1,0,1} { \draw [presDark] (-2,\i) -- (2,\i); } \foreach \i in {-1.50465122, -0.76393202, 0.34370152} { \draw [presDark] (\i, -2) -- (\i, 2); } \draw [latex-, presDark] (2,2) -- (2.4,2.2) node [right] {Storage $\begin{pmatrix}\textrm{Static}\\ \textrm{Dynamic}\end{pmatrix}$}; % Left axis \draw [presDark2] (-2.3,-2) -- (-2.3,2); \foreach \i in {-2,-1,0,1,2} { \draw [presDark2] (-2.45,\i) -- (-2.15,\i); } \node at (-2.3,2) [above, presDark2] {Regular axis}; % Bottom axis \draw [presDark2] (-2,-2.3) -- (2,-2.3); \foreach \i in {-2. , -1.50465122, -0.76393202, 0.34370152, 2.} { \draw [presDark2] (\i, -2.45) -- (\i, -2.15); } \node at (2,-2.2) [presDark2, right=.4cm, align=left] {Regular axis with\\log transform}; % Axes \begin{scope}[xshift=-2.9cm, yshift=-2.3cm, rotate=45] \draw [presDark2, decorate, decoration={brace,amplitude=10pt}, xshift=-4pt, yshift=0pt] (0.5,-0.7) -- (0.5,0.7); \node at (-.3,0) [presDark2] {axes}; \end{scope} % Overflow bin \draw [presLight2] (2.05,-2.3) -- (2.4, -2.3); \draw [presLight2] (2.4, -2.45) -- (2.4, -2.15); \draw [presLight2, latex- ] (2.2, -2.3) -- (2.2, -2.6) node [below] {Optional overflow}; % Underflow bin \draw [presLight2] (-2.05,-2.3) -- (-2.4, -2.3); \draw [presLight2] (-2.4, -2.45) -- (-2.4, -2.15); \draw [presLight2, latex- ] (-2.25, -2.3) -- (-1.8, -2.6) node [below] {Optional underflow}; % Accumulators \node (accum) at (1.2,1.5) [circle, minimum width=.7cm, draw, presLight] {}; \draw [presLight, latex-] (accum) -- (2.25, .5) node [right, align=left] {Accumulator\\int, double,\\unlimited, ...}; \end{tikzpicture} \end{document} scikit-hep-boost-histogram-5f6f3e3/docs/api/000077500000000000000000000000001516627434300210305ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/api/boost_histogram.axis.rst000066400000000000000000000004601516627434300257300ustar00rootroot00000000000000boost\_histogram.axis ===================== .. automodule:: boost_histogram.axis :members: :undoc-members: :show-inheritance: boost\_histogram.axis.transform =============================== .. automodule:: boost_histogram.axis.transform :members: :undoc-members: :show-inheritance: scikit-hep-boost-histogram-5f6f3e3/docs/api/boost_histogram.rst000066400000000000000000000022271516627434300247700ustar00rootroot00000000000000boost\_histogram ================ .. automodule:: boost_histogram :members: :undoc-members: :show-inheritance: .. include:: boost_histogram.axis.rst boost\_histogram.accumulators ============================= .. automodule:: boost_histogram.accumulators :members: :undoc-members: :show-inheritance: boost\_histogram.histogram ========================== .. automodule:: boost_histogram.histogram :members: :undoc-members: :show-inheritance: boost\_histogram.numpy ====================== .. automodule:: boost_histogram.numpy :members: :undoc-members: :show-inheritance: boost\_histogram.storage ======================== .. automodule:: boost_histogram.storage :members: :undoc-members: :show-inheritance: boost\_histogram.tag ==================== .. automodule:: boost_histogram.tag :members: :undoc-members: :show-inheritance: boost\_histogram.typing ======================= .. automodule:: boost_histogram.typing :members: :undoc-members: :show-inheritance: boost\_histogram.view ===================== .. automodule:: boost_histogram.view :members: :undoc-members: :show-inheritance: scikit-hep-boost-histogram-5f6f3e3/docs/api/modules.rst000066400000000000000000000000621516627434300232300ustar00rootroot00000000000000 .. toctree:: :maxdepth: 4 boost_histogram scikit-hep-boost-histogram-5f6f3e3/docs/banner_slides.md000066400000000000000000000037641516627434300234230ustar00rootroot00000000000000--- aspectratio: 169 fontfamily: bookman orphan: true --- # Simple usage ```python import boost_histogram as bh # Make a histogram h = bh.Histogram(bh.axis.Regular(10, 0, 1)) # Fill it with events h.fill([0.2, 0.3, 0.6, 0.9]) # Compute the sum total = h.sum() ``` # Advanced Indexing ```python # Slice in data coordinates sliced_h = h[bh.loc(0.5) : bh.loc(1.5)] # Sum over and rebin easily smaller = h[::sum, :: bh.rebin(2)] # Set and access easily h[...] = np.asarray(prebinned) # Project and reorder axes h2 = h.project(1, 0) ``` # Many Axis types ::: columns ::: {.column width="60%"} - Regular: evenly or functionally spaced binning - Variable: arbitrary binning - Integer: integer binning - IntCategory: arbitrary integers - StrCategory: strings - Boolean: True/False Most Axis types support optional extras: - Underflow and/or Overflow bins - Growth - Circular (wraparound) ::: ::: {.column width="40%"} \begin{center} \includegraphics[width=1.05\textwidth]{docs/\_images/axis_circular.png} \includegraphics[width=.7\textwidth]{docs/\_images/axis_regular.png} \includegraphics[width=.9\textwidth]{docs/\_images/axis_variable.png} \includegraphics[width=.65\textwidth]{docs/\_images/axis_integer.png} \includegraphics[width=\textwidth]{docs/\_images/axis_category.png} \end{center} ::: ::: # Advanced Storages - Integers - Floats - Weighted Sums - Means - Weighted Means Supports the UHI `PlottableHistogram` protocol! ```python import mplhep mplhep.histplot(h) ``` # NumPy compatibility ```python # Drop in faster replacement H, edges = bh.numpy.histogram(data) # Drop in threaded faster replacement H, edges = bh.numpy.histogram(data, threads=4) # Drop in much faster replacement for 2D+ H, e1, e2 = bh.numpy.histogram2d(xdata, ydata) # Return a Histogram, convert back to NumPy style h = bh.numpy.histogram(data, histogram=bh.Histogram) H, edges = h.to_numpy() ``` scikit-hep-boost-histogram-5f6f3e3/docs/changelog.md000066400000000000000000001273751516627434300225470ustar00rootroot00000000000000# Changelog ## Version 1.7 ### Version 1.7.2 #### Features - No storage option/support for serialization, by @henryiii in [#1098][] #### Fixes - Correct repr for MultiCell by @henryiii in [#1087][] - Fix serialization round-trip for 3D histograms with empty axes by @Copilot in [#1094][] - Fix `__setitem__` axis index mismatch when slices are not leading indices by @Copilot in [#1097][] - Support 0D MeanView/WeightedMeanView by @Copilot in [#1100][] - Broadcast scalar axis args to match array sample length in Mean/WeightedMean fill by @Copilot in [#1101][] #### Typing - Slightly better getitem type by @henryiii in [#1086][] #### Internal - Use pattern matching in another place by @henryiii in [#1092][] - Move to using prek by @henryiii in [#1105][] - Add `AGENTS.md` by @henryiii in [#1106][] [#1098]: https://github.com/scikit-hep/boost-histogram/pull/1098 [#1087]: https://github.com/scikit-hep/boost-histogram/pull/1087 [#1094]: https://github.com/scikit-hep/boost-histogram/pull/1094 [#1097]: https://github.com/scikit-hep/boost-histogram/pull/1097 [#1100]: https://github.com/scikit-hep/boost-histogram/pull/1100 [#1101]: https://github.com/scikit-hep/boost-histogram/pull/1101 [#1086]: https://github.com/scikit-hep/boost-histogram/pull/1086 [#1092]: https://github.com/scikit-hep/boost-histogram/pull/1092 [#1105]: https://github.com/scikit-hep/boost-histogram/pull/1105 [#1106]: https://github.com/scikit-hep/boost-histogram/pull/1106 ### Version 1.7.1 #### Typing - Make the `Histogram` type generic, by @henryiii in [#1081][], [#1082][], [#1083][] - The `MultiCell` storage takes an argument (missed in typing), by @henryiii in [#1080][] [#1080]: https://github.com/scikit-hep/boost-histogram/pull/1080 [#1081]: https://github.com/scikit-hep/boost-histogram/pull/1081 [#1082]: https://github.com/scikit-hep/boost-histogram/pull/1082 [#1083]: https://github.com/scikit-hep/boost-histogram/pull/1083 ### Version 1.7.0 #### Features - `MultiCell` storage, supporting a variable number of values, by @Superharz in [#1008][] - Record `growth=True` in writer-info (serialization), and respect it if present by @henryiii in [#1074][] - Drop Python 3.9 by @henryiii in [#1057][] - Drop Python 3.13t (use 3.14t instead) by @henryiii in [#1079][] #### Bug fixes - Don't add empty metadata dicts by @henryiii in [#1048][] - Avoid compiler warning: declaration of 'i' hides previous local declaration by @Superharz in [#1068][] #### Internal - Refactor `__getitem__` using pattern matching by @henryiii in [#1067][] - pytest `log_level` is better than `log_cli_level` by @henryiii in [#1059][] - Setting `__file__` no longer required (pybind11 3.0) by @henryiii in [#1066][] - This should work on iOS now in cibuildwheel by @henryiii in [#1049][] [#1008]: https://github.com/scikit-hep/boost-histogram/pull/1008 [#1048]: https://github.com/scikit-hep/boost-histogram/pull/1048 [#1049]: https://github.com/scikit-hep/boost-histogram/pull/1049 [#1057]: https://github.com/scikit-hep/boost-histogram/pull/1057 [#1059]: https://github.com/scikit-hep/boost-histogram/pull/1059 [#1066]: https://github.com/scikit-hep/boost-histogram/pull/1066 [#1067]: https://github.com/scikit-hep/boost-histogram/pull/1067 [#1068]: https://github.com/scikit-hep/boost-histogram/pull/1068 [#1074]: https://github.com/scikit-hep/boost-histogram/pull/1074 [#1079]: https://github.com/scikit-hep/boost-histogram/pull/1079 ## Version 1.6 ### Version 1.6.1 This release fixes several issues with serialization as it's being prepared in Hist/uhi. #### Features - Support `__dict__` on histogram like axes [#1041][] - Allow library to be specified in `writer_info` removal [#1042][] #### Bug fixes - Avoid mutating lists given in indexing [#1047][] - Histogram metadata not being serialized correctly [#1038][] - Unset classic metadata shouldn't show up in serialization [#1039][] - Set dict correctly when unserializing [#1040][] - Serialization was broken for N>1D complex storage histograms [#1043][] [#1038]: https://github.com/scikit-hep/boost-histogram/pull/1038 [#1039]: https://github.com/scikit-hep/boost-histogram/pull/1039 [#1040]: https://github.com/scikit-hep/boost-histogram/pull/1040 [#1041]: https://github.com/scikit-hep/boost-histogram/pull/1041 [#1042]: https://github.com/scikit-hep/boost-histogram/pull/1042 [#1043]: https://github.com/scikit-hep/boost-histogram/pull/1043 [#1047]: https://github.com/scikit-hep/boost-histogram/pull/1047 ### Version 1.6.0 This release adds support for UHI serialization for use with the `uhi` library, supported in the next release of `uhi`. A new diagnostic test has been added in the wheel, so you can quickly check your compile of boost-histogram without the full test suite. Quite a few fixes are present, as well, including a fix for `*=`, setting a range with a scalar, setting with a callable in a slice. Backend changes include many new clang-tidy checks that reduce the number of copies and refcount changes. We now build with pybind11 3.0. This release drops support for Python 3.8, and adds Python 3.14(t), iOS, Windows ARM, and GraalPy. #### Features - Initial support for uhi's serialization [#997][], [#1030][], [#1033][], [#1034][], [#1035][] - Drop Python 3.8 [#976][] - Support Windows ARM [#1001][] - Support Python 3.14 and 3.14t [#1018][] - Support iOS [#1020][], [#1027][] - Added diagnostics test [#1022][] - Recommend/use `np.s_` for slicing in dicts [#1002][] #### Bug fixes - Support setting ranges with histograms (fixes `*=` too) [#1036][] - Support a callable in range setting [#1036][] - Support setting a range with a scalar [#1000][] - Account for fuzzy edge values in rebinning with edges [#999][] - Rename C++ metadata to `raw_metadata` [#979][] - Address lots of clang-tidy suggestions, should be faster with less copies/refcount bumps [#1009][], [#1010][], [#1011][], [#1012][], [#1013][], [#1014][], [#1015][] #### CI and testing - Add CMake presets for testing and clang-tidy [#1009][] [#976]: https://github.com/scikit-hep/boost-histogram/pull/976 [#979]: https://github.com/scikit-hep/boost-histogram/pull/979 [#997]: https://github.com/scikit-hep/boost-histogram/pull/997 [#999]: https://github.com/scikit-hep/boost-histogram/pull/999 [#1000]: https://github.com/scikit-hep/boost-histogram/pull/1000 [#1001]: https://github.com/scikit-hep/boost-histogram/pull/1001 :[#1002]: https://github.com/scikit-hep/boost-histogram/pull/1002 [#1009]: https://github.com/scikit-hep/boost-histogram/pull/1009 [#1010]: https://github.com/scikit-hep/boost-histogram/pull/1010 [#1011]: https://github.com/scikit-hep/boost-histogram/pull/1011 [#1012]: https://github.com/scikit-hep/boost-histogram/pull/1012 [#1013]: https://github.com/scikit-hep/boost-histogram/pull/1013 [#1014]: https://github.com/scikit-hep/boost-histogram/pull/1014 [#1015]: https://github.com/scikit-hep/boost-histogram/pull/1015 [#1018]: https://github.com/scikit-hep/boost-histogram/pull/1018 [#1020]: https://github.com/scikit-hep/boost-histogram/pull/1020 [#1022]: https://github.com/scikit-hep/boost-histogram/pull/1022 [#1027]: https://github.com/scikit-hep/boost-histogram/pull/1027 [#1030]: https://github.com/scikit-hep/boost-histogram/pull/1030 [#1033]: https://github.com/scikit-hep/boost-histogram/pull/1033 [#1034]: https://github.com/scikit-hep/boost-histogram/pull/1034 [#1035]: https://github.com/scikit-hep/boost-histogram/pull/1035 [#1036]: https://github.com/scikit-hep/boost-histogram/pull/1036 ## Version 1.5 ### Version 1.5.2 Fix for axis metadata not passing though non-uniform rebinning correctly. Flow bins are now preserved when doing a non-uniform rebinning. Also adds the ability to rebin by edges or an existing axis. This is expected to be the last release with Python 3.8 support. Manylinux support will likely be bumped from `2024` to `2_28` soon, as well. #### Features - Support `edges=` and `axis=` in `bh.rebin` [#977][] - Allow axis to be passed through `bh.rebin` [#980][], [#982][] #### Bug fixes - Axis metadata was broken when rebinning [#978][] - Flow bins were lost when using variable rebinning [#977][] - Scikit-build-core 0.11, with PEP 639 license metadata [#986][], [#988][] - Prepare for an potential upcoming pybind11 3 change [#994][] #### CI and testing - Remove extras like `[test]` in favor of dependency-groups [#973][] - Multithreaded test running [#987][] [#959]: https://github.com/scikit-hep/boost-histogram/pull/959 [#973]: https://github.com/scikit-hep/boost-histogram/pull/973 [#977]: https://github.com/scikit-hep/boost-histogram/pull/977 [#978]: https://github.com/scikit-hep/boost-histogram/pull/978 [#980]: https://github.com/scikit-hep/boost-histogram/pull/980 [#982]: https://github.com/scikit-hep/boost-histogram/pull/982 [#986]: https://github.com/scikit-hep/boost-histogram/pull/986 [#987]: https://github.com/scikit-hep/boost-histogram/pull/987 [#988]: https://github.com/scikit-hep/boost-histogram/pull/988 [#994]: https://github.com/scikit-hep/boost-histogram/pull/994 ### Version 1.5.1 #### Bug fixes - Make non-uniform rebinning work for Weight() and friends [#972][] [#972]: https://github.com/scikit-hep/boost-histogram/pull/972 ### Version 1.5.0 #### Features - Support variable rebinning [#913][] - Removed support for Python 3.7 [#952][] - Added support for Python 3.13 and free-threaded Python 3.13t [#950][] - Add GitHub artifact attestations to package distribution [#933][] #### Backend and docs - Move build to scikit-build-core [#887][] - Clean up sdist files [#928][] - Minor touchup to build backend, use scikit-build-core 0.10+ [#948][] #### CI and testing - Try cibuildwheel pyodide [#935][] - Try to fix pyodide build [#934][] - Update emscripten build [#937][] - Bump to Pyodide 3.12 [#938][] - Speed up pyodide job by a few seconds [#941][] - Update jobs and noxfile [#929][] - Use pylint 3.2 github formatter instead [#932][] - Add some CI speedups [#939][] - Faster cibuildwheel [#942][] - Adapt to final release of NumPy 2.1.0 [#951][] [#887]: https://github.com/scikit-hep/boost-histogram/pull/887 [#913]: https://github.com/scikit-hep/boost-histogram/pull/913 [#928]: https://github.com/scikit-hep/boost-histogram/pull/928 [#929]: https://github.com/scikit-hep/boost-histogram/pull/929 [#932]: https://github.com/scikit-hep/boost-histogram/pull/932 [#933]: https://github.com/scikit-hep/boost-histogram/pull/933 [#934]: https://github.com/scikit-hep/boost-histogram/pull/934 [#935]: https://github.com/scikit-hep/boost-histogram/pull/935 [#937]: https://github.com/scikit-hep/boost-histogram/pull/937 [#938]: https://github.com/scikit-hep/boost-histogram/pull/938 [#939]: https://github.com/scikit-hep/boost-histogram/pull/939 [#941]: https://github.com/scikit-hep/boost-histogram/pull/941 [#942]: https://github.com/scikit-hep/boost-histogram/pull/942 [#948]: https://github.com/scikit-hep/boost-histogram/pull/948 [#950]: https://github.com/scikit-hep/boost-histogram/pull/950 [#951]: https://github.com/scikit-hep/boost-histogram/pull/951 [#952]: https://github.com/scikit-hep/boost-histogram/pull/952 ## Version 1.4 ### Version 1.4.1 #### Features - NumPy 2 support. [#918][] - 32-bit Windows Python 3.12 wheel added (matching NumPy). [#920][] #### Bugfixes - Support filling Integer axes with unsigned integers [#917][] - Avoid triggering NumPy 2 dev release install on Python 3.12. [#914][] #### Backend and docs - Add missing API docs [#909][] - Use boost 1.84 [#920][] [#909]: https://github.com/scikit-hep/boost-histogram/pull/909 [#914]: https://github.com/scikit-hep/boost-histogram/pull/914 [#917]: https://github.com/scikit-hep/boost-histogram/pull/917 [#918]: https://github.com/scikit-hep/boost-histogram/pull/918 [#920]: https://github.com/scikit-hep/boost-histogram/pull/920 ### Version 1.4.0 #### Features - `overflow=False` is now supported for `IntCategory` and `StrCategory`. [#883][] #### Changes - Using `_storage_type` now produces a `DeprecationWarning` instead of `PendingDeprecationWarning`. [#801][] - Updated Boost to 1.82. The upper limit on Regular axes without overflow is now inclusive like NumPy. [#802][] - Produce more detailed error messages on C++ errors [#848][] #### Bugfixes - Make filling an integer axis with a float array (also) an error. [#876][] - Include `-latomic` on `armv7l` [#823][] #### Backend and docs - Add Python 3.12 support and binary wheels, also latest PyPy. `manylinux2014+` required. [#880][], [#878][] - Drop Python 3.6 support. [#798][] - Drop pre-built wheels for 32-bit Linux (NumPy also dropped). [#849][] - Add testing for WebAssembly (Pyodide). [#850][] - Use Ruff [#829][] [#798]: https://github.com/scikit-hep/boost-histogram/pull/798 [#801]: https://github.com/scikit-hep/boost-histogram/pull/801 [#802]: https://github.com/scikit-hep/boost-histogram/pull/802 [#823]: https://github.com/scikit-hep/boost-histogram/pull/823 [#829]: https://github.com/scikit-hep/boost-histogram/pull/829 [#848]: https://github.com/scikit-hep/boost-histogram/pull/848 [#849]: https://github.com/scikit-hep/boost-histogram/pull/849 [#850]: https://github.com/scikit-hep/boost-histogram/pull/850 [#876]: https://github.com/scikit-hep/boost-histogram/pull/876 [#878]: https://github.com/scikit-hep/boost-histogram/pull/878 [#880]: https://github.com/scikit-hep/boost-histogram/pull/880 [#883]: https://github.com/scikit-hep/boost-histogram/pull/883 ## Version 1.3 ### Version 1.3.2 #### Changes - Added `storage_type` operator and `storage_type()` function [#781][], with pending deprecation for `_storage_type`. [#786][] [#790][] - Better errors generated for missing or incorrect sample to mean storage. [#782][] - Better error message when views are set with an incompatible array. [#794][] #### Bug fixes - Patch broken sum with fully empty (0 bin) axis. [#718][] - Fix zero range `bh.numpy.histogram` to match `numpy.histogram` behavior. [#721][] - Avoid triggering `__init__` when copying (better support for subclasses with custom init's). [#759][] - IntCategory now supports numbers larger than 2^24 (now 2^53). [#792][] - Pick a subset now supported inside a larger expression. [#793][] #### Backend and docs - Minor optimizations for UFuncs. [#771][] - Added Python 3.11 wheels. [#789][] - Include PyPy 3.9 binary wheels. [#730][] - Using pybind11 2.10 [#767][] - Explicit `reset()` documentation. [#783][] - Minor cleanup and further removal of a little Python 2 back-compat code. - Warnings have better stacklevel settings. [#718]: https://github.com/scikit-hep/boost-histogram/pull/718 [#721]: https://github.com/scikit-hep/boost-histogram/pull/721 [#730]: https://github.com/scikit-hep/boost-histogram/pull/730 [#759]: https://github.com/scikit-hep/boost-histogram/pull/759 [#767]: https://github.com/scikit-hep/boost-histogram/pull/767 [#771]: https://github.com/scikit-hep/boost-histogram/pull/771 [#781]: https://github.com/scikit-hep/boost-histogram/pull/781 [#782]: https://github.com/scikit-hep/boost-histogram/pull/782 [#783]: https://github.com/scikit-hep/boost-histogram/pull/783 [#786]: https://github.com/scikit-hep/boost-histogram/pull/786 [#789]: https://github.com/scikit-hep/boost-histogram/pull/789 [#790]: https://github.com/scikit-hep/boost-histogram/pull/790 [#792]: https://github.com/scikit-hep/boost-histogram/pull/792 [#793]: https://github.com/scikit-hep/boost-histogram/pull/793 [#794]: https://github.com/scikit-hep/boost-histogram/pull/794 ### Version 1.3.1 #### Bug fixes - Fixed regression with invalid `.project` input causing segfaults. [#708][] - Minor skips for specific tests on ppc64le, primarily for a NumPy bug. [#707][] - Avoid using EH for program control, better on Pyodide. [#709][] - Fix regression with exact float not being accepted for `.index` for `IntCategory` in 1.3.0. Add `hist` nox session to check downstream (manually for the moment). [#710][] [#707]: https://github.com/scikit-hep/boost-histogram/pull/707 [#708]: https://github.com/scikit-hep/boost-histogram/pull/708 [#709]: https://github.com/scikit-hep/boost-histogram/pull/709 [#710]: https://github.com/scikit-hep/boost-histogram/pull/710 ### Version 1.3.0 #### User changes - PyPy 3.8 now supported with binary wheels. [#677][] - The GIL is released a little more often now. [#662][] - AxesTuple does not allow construction of non-axes. [#680][] - KeyError is now thrown when accessing a non-existent item in a Category Axis [#689][] - WeightedViews now support `np.cumsum` [#699][] #### Bug fixes - Fixed WeightedMean storages producing NaN for .variances() [#695][] - Modify local include slightly to enable WebAssembly compilation in Pyodide [#702][] #### Developer changes - Use PyLint in CI to check for some style issues [#690][] - Developer (CMake) installs no longer require toml [#698][] [#662]: https://github.com/scikit-hep/boost-histogram/pull/662 [#677]: https://github.com/scikit-hep/boost-histogram/pull/677 [#680]: https://github.com/scikit-hep/boost-histogram/pull/680 [#689]: https://github.com/scikit-hep/boost-histogram/pull/689 [#690]: https://github.com/scikit-hep/boost-histogram/pull/690 [#695]: https://github.com/scikit-hep/boost-histogram/pull/695 [#698]: https://github.com/scikit-hep/boost-histogram/pull/698 [#699]: https://github.com/scikit-hep/boost-histogram/pull/699 [#702]: https://github.com/scikit-hep/boost-histogram/pull/702 ## Version 1.2 ### Version 1.2.1 #### User changes - musllinux wheels now provided along with manylinux [#656][] #### Bug fixes - Fixed single-element negative growth fill [#654][] #### Developer changes - No longer require Docker for clang-format, runs online too [#610][] - Using pybind11 2.8.0 [#658][] [#610]: https://github.com/scikit-hep/boost-histogram/pull/610 [#654]: https://github.com/scikit-hep/boost-histogram/pull/654 [#656]: https://github.com/scikit-hep/boost-histogram/pull/656 [#658]: https://github.com/scikit-hep/boost-histogram/pull/658 ### Version 1.2.0 #### User changes - Python 3.10 officially supported, with wheels. - Support subtraction on histograms [#636][] - Integer histograms are now signed [#636][] #### Bug fixes - Support custom setters on AxesTuple subclasses. [#627][] - Faster picking if slices are not also used [#645][] or if they are [#648][] (1000x or more in some cases) - Throw an error when an AxesTuple setter is the wrong length (inspired by zip strict in Python 3.10) [#627][] - Fix error thrown on comparison with axis and non-axis object [#631][] - Static typing no longer thinks `storage=` is required [#604][] #### Developer changes - Support NumPy 1.21 for static type checking [#625][] - Use newer Boost 1.77 and Boost.Histogram 1.77+1 [#594][] - Provide nox support [#647][] [#594]: https://github.com/scikit-hep/boost-histogram/pull/594 [#604]: https://github.com/scikit-hep/boost-histogram/pull/604 [#625]: https://github.com/scikit-hep/boost-histogram/pull/625 [#627]: https://github.com/scikit-hep/boost-histogram/pull/627 [#631]: https://github.com/scikit-hep/boost-histogram/pull/631 [#636]: https://github.com/scikit-hep/boost-histogram/pull/636 [#645]: https://github.com/scikit-hep/boost-histogram/pull/645 [#647]: https://github.com/scikit-hep/boost-histogram/pull/647 [#648]: https://github.com/scikit-hep/boost-histogram/pull/648 ## Version 1.1 ### Version 1.1.0 #### User changes - Experimentally support list selection on categorical axes [#577][] - Support Python 3.8 on Apple Silicon [#600][] - Scaling and addition with a scalar affect flow bins too [#580][] - Change `sum_of_deltas_squared` to `_sum_of_deltas_squared` (was an implementation detail) [#602][] #### Bug fixes - Fix "picking" on a flow bin [#576][] - Better error message on getattr [#596][] #### Developer changes - Test on Python 3.10 beta releases [#600][] - Provide a CMakeLists for quick standalone Boost.Histogram C++ experiments [#591][] - Adding logging with pytest failure output [#575][] [#575]: https://github.com/scikit-hep/boost-histogram/pull/575 [#576]: https://github.com/scikit-hep/boost-histogram/pull/576 [#577]: https://github.com/scikit-hep/boost-histogram/pull/577 [#580]: https://github.com/scikit-hep/boost-histogram/pull/580 [#591]: https://github.com/scikit-hep/boost-histogram/pull/591 [#596]: https://github.com/scikit-hep/boost-histogram/pull/596 [#600]: https://github.com/scikit-hep/boost-histogram/pull/600 [#602]: https://github.com/scikit-hep/boost-histogram/pull/602 ## Version 1.0 ### Version 1.0.2 - Fix scaling a weighted storage [#559][] - Fix partial summation over a Categorical axis [#564][] - Support running type checking from Python < 3.8 [#542][] [#542]: https://github.com/scikit-hep/boost-histogram/pull/542 [#559]: https://github.com/scikit-hep/boost-histogram/pull/559 [#564]: https://github.com/scikit-hep/boost-histogram/pull/564 ### Version 1.0.1 #### Subclassing Histogram changes - A `family=` is no longer required if you _only_ subclass Histogram. [#533][] #### Bug fixes - Fix summing of Mean/WeightedMean accumulators [#537][] - Added missing dependency on `typing_extensions` for Python 3.6 & 3.7 [#529][] #### Typing changes - Added Ellipsis support to typing. [#525][] - Better typing for Views. [#530][] - Fixed issue with Histogram copy constructor requiring metadata [#532][] [#525]: https://github.com/scikit-hep/boost-histogram/pull/525 [#526]: https://github.com/scikit-hep/boost-histogram/pull/526 [#529]: https://github.com/scikit-hep/boost-histogram/pull/529 [#530]: https://github.com/scikit-hep/boost-histogram/pull/530 [#532]: https://github.com/scikit-hep/boost-histogram/pull/532 [#533]: https://github.com/scikit-hep/boost-histogram/pull/533 [#537]: https://github.com/scikit-hep/boost-histogram/pull/537 ### Version 1.0.0 Dropped support for Python 2 and 3.5; removed large numbers of workarounds. Fully statically typed. API compatible with the final `0.x` release for most uses, except for subclassing; subclassing histogram components now uses Python 3 class keyword syntax to set families. #### User changes - Dropped Python 2.7 and 3.5 support [#512][] - Removed deprecated `.options` from axes. Use `.traits` instead. [#503][] - Full static typing available, UHI 0.1.2+ supported. [#516][], [#517][], [#519][], [#520][], [#521][], [#523][] #### Subclassing Histogram changes - Use keyword class family setting when subclassing histogram components instead of custom decorator. [#513][] - Structure of internal repr creation changed and made slightly more public. [#518][] #### Bug fixes - Consistently show `metadata=` in repr if present; refactored internal repr handling [#518][] - Minor typing related fixes for rare bugs (especially in `numpy.py`, [#521][]) [#503]: https://github.com/scikit-hep/boost-histogram/pull/503 [#512]: https://github.com/scikit-hep/boost-histogram/pull/512 [#513]: https://github.com/scikit-hep/boost-histogram/pull/513 [#516]: https://github.com/scikit-hep/boost-histogram/pull/516 [#517]: https://github.com/scikit-hep/boost-histogram/pull/517 [#518]: https://github.com/scikit-hep/boost-histogram/pull/518 [#519]: https://github.com/scikit-hep/boost-histogram/pull/519 [#520]: https://github.com/scikit-hep/boost-histogram/pull/520 [#521]: https://github.com/scikit-hep/boost-histogram/pull/521 [#523]: https://github.com/scikit-hep/boost-histogram/pull/523 ## Version 0.13 ### Version 0.13.2 - Backport fix scaling a weighted storage - Backport fix partial summation over a Categorical axis ### Version 0.13.1 - Backport fix for Mean/WeightedMean summing. - Backport fix for `boost_histogram.numpy` density. - Backport missing metadata from the repr's. - Ignore `family=` on Histogram subclassing to make subclassing Histogram only possible in 1.x + 0.x code. ### Version 0.13.0 PlottableProtocol provides a way to plot in different libraries, and easy access to common quantities. This is expected to be the final release for Python 2, and mostly equivalent in API to 1.0. #### User changes - Support for PlottableProtocol. You can now access `.values()`, `.counts()`, and `.variances()` on all storages; used by plotting libraries. `.kind` describes the Kind of the histogram (`bh.Kind.COUNT` or `bh.Kind.MEAN`). `.options` has been renamed to `.traits`, and a few more useful traits were added, like `.discrete`. Most other portions of the Protocol were already present. [#476][] - Removed deprecated `.rank` on histograms (since 0.8). Use `.ndim` instead. [#505][] - Supports converting user histogram objects that provide a `_to_boost_histogram_` method. [#483][] - A `view=True` parameter must now be passed to get a View instead of a standard NumPy values array from `to_numpy()`. [#498][] #### Bug fixes - Added additional support for typing, fixing a couple of rare Python 2 bugs in the process [#493][]. - The resulting histogram from `bh.numpy.*` functions is now reducible [#508][] #### Developer changes - Use GitHub Actions for ARM compiling [#474][] - Apple Silicon support (since 0.12) [#495][] - Support compiling with C++17 [#502][] - Rename `NPY_NUM_BUILD_JOBS` to `CMAKE_BUILD_PARALLEL_LEVEL` for consistency with other Scikit-HEP projects. [#502][] [#474]: https://github.com/scikit-hep/boost-histogram/pull/474 [#476]: https://github.com/scikit-hep/boost-histogram/pull/476 [#483]: https://github.com/scikit-hep/boost-histogram/pull/483 [#493]: https://github.com/scikit-hep/boost-histogram/pull/493 [#495]: https://github.com/scikit-hep/boost-histogram/pull/495 [#498]: https://github.com/scikit-hep/boost-histogram/pull/498 [#502]: https://github.com/scikit-hep/boost-histogram/pull/502 [#505]: https://github.com/scikit-hep/boost-histogram/pull/505 [#508]: https://github.com/scikit-hep/boost-histogram/pull/508 ## Version 0.12 ### Version 0.12.0 Pressing forward to 1.0. #### User changes - You can now set all complex storages, either on a Histogram or a View with an (N+1)D array [#475][] - Axes are now normal `__dict__` classes, you can manipulate the `__dict__` as normal. Axes construction now lets you either use the old metadata shortcut or the `__dict__` inline. [#477][] #### Bug fixes - Fixed slicing projection with one-sided slices [#479][] - Fixed issue if final bin of Variable histogram was infinite by updating to Boost 1.75 [#470][] - NumPy arrays can be used for weights in `bh.numpy` [#472][] - Vectorization for WeightedMean accumulators was broken [#475][] #### Developer changes - Bumped to pybind11 version [#470][] - Black formatting used in notebooks too [#470][] #### Upgrade warning If you are using `Axis.options`, please transition to `Axis.traits`. `traits` includes all the old options, along with some new traits, and matches the PlottableProtocol requirements. [#470]: https://github.com/scikit-hep/boost-histogram/pull/470 [#472]: https://github.com/scikit-hep/boost-histogram/pull/472 [#475]: https://github.com/scikit-hep/boost-histogram/pull/475 [#477]: https://github.com/scikit-hep/boost-histogram/pull/477 [#479]: https://github.com/scikit-hep/boost-histogram/pull/479 ## Version 0.11 ### Version 0.11.1 Updating pybind11 to 2.6.0. [#443][] Features: - Python 3.9 support - PyPy2 / PyPy3.6 / PyPy3.7 support - Warnings on latest AppleClang fixed - 40% faster accumulator fills, simpler implementation - Segfaults when passing an object with a throwing repr fixed - kwargs replaced older workarounds (partially at the moment) - Using new `py::type` instead of `pybind11::detail` usage - Enhanced CMake support, finds conda and venv now, uses `pybind11_find_import` - Using setuptools support from pybind11 (previously vendored, so benefits have been available since 0.11.0) Also cleans up SDists a bit. [#467][] [#443]: https://github.com/scikit-hep/boost-histogram/pull/443 [#467]: https://github.com/scikit-hep/boost-histogram/pull/467 ### Version 0.11.0 A release focused on preparing for the upcoming Hist 2.0 release. #### User changes - Arbitrary items can be set on an axis or histogram. [#450][], [#456][] - Subclasses can customize the conversion procedure. [#456][] #### Bug fixes - Fixed reading pickles from boost-histogram 0.6-0.8 [#445][] - Minor correctness fix [#446][] - Accidental install of typing on Python 3.5+ fixed - Scalar ND fill fixed [#453][] #### Developer changes - Updated to Boost 1.74 [#442][] - CMake installs version.py now too [#449][] - Updated setuptools infrastructure no longer requires NumPy [#451][] - Some basic clang-tidy checks are now being run [#455][] [#442]: https://github.com/scikit-hep/boost-histogram/pull/442 [#445]: https://github.com/scikit-hep/boost-histogram/pull/445 [#446]: https://github.com/scikit-hep/boost-histogram/pull/446 [#449]: https://github.com/scikit-hep/boost-histogram/pull/449 [#450]: https://github.com/scikit-hep/boost-histogram/pull/450 [#451]: https://github.com/scikit-hep/boost-histogram/pull/451 [#453]: https://github.com/scikit-hep/boost-histogram/pull/453 [#455]: https://github.com/scikit-hep/boost-histogram/pull/455 [#456]: https://github.com/scikit-hep/boost-histogram/pull/456 ## Version 0.10 ### Version 0.10.2 Quick fix for extra print statement in fill. #### Bug fixes - Fixed debugging print statement in fill. [#438][] #### Developer changes - Added CI/pre-commit check for print statements [#438][] - Formatting CMakeLists now too [#439][] [#438]: https://github.com/scikit-hep/boost-histogram/pull/438 [#439]: https://github.com/scikit-hep/boost-histogram/pull/439 ### Version 0.10.1 Several fixes were made, mostly related to Weight storage histograms from Uproot 4. #### Bug fixes - Reduction on `h.axes.widths` supported again [#428][] - WeightedSumView supports standard array operations [#432][] - Operations shallow copy (non-copyable metadata supported) [#433][] - Pandas Series as samples/weights supported [#434][] - Support NumPy scalars in operations [#436][] [#428]: https://github.com/scikit-hep/boost-histogram/pull/428 [#432]: https://github.com/scikit-hep/boost-histogram/pull/432 [#433]: https://github.com/scikit-hep/boost-histogram/pull/433 [#434]: https://github.com/scikit-hep/boost-histogram/pull/434 [#436]: https://github.com/scikit-hep/boost-histogram/pull/436 ### Version 0.10.0 This version was released during PyHEP 2020. Several improvements were made to usability when plotting and indexing. #### User changes - AxesTuple array now support operations via ArrayTuple [#414][] - Support `sum` and `bh.rebin` without slice [#424][] - Nicer error messages in some cases [#415][] - Made a few properties hidden for accumulators that were not public [#418][] - Boolean now supports reduction, faster compile [#422][] - AxesTuple now available publicly for subprojects [#419][] #### Bug fixes - Histograms support operations with arrays, no longer take the first element only [#417][] [#414]: https://github.com/scikit-hep/boost-histogram/pull/414 [#415]: https://github.com/scikit-hep/boost-histogram/pull/415 [#417]: https://github.com/scikit-hep/boost-histogram/pull/417 [#418]: https://github.com/scikit-hep/boost-histogram/pull/418 [#419]: https://github.com/scikit-hep/boost-histogram/pull/419 [#422]: https://github.com/scikit-hep/boost-histogram/pull/422 [#424]: https://github.com/scikit-hep/boost-histogram/pull/424 ## Version 0.9 ### Version 0.9.0 This version was released just before PyHEP 2020. Several important fixes were made, along with a few new features to better support downstream projects. #### User changes - `metadata` supported and propagated on Histograms (slots added) [#403][] - Added `dd=True` option in `to_numpy` [#406][] - Deprecated `cpp` module removed [#402][] #### Developer changes - Subclasses can override axes generation [#401][] - `[dev]` extra now installs `pytest` [#401][] #### Bug fixes - Fix `numpy.histogramdd` return structure [#406][] - Travis deploy multi-arch fixes [#399][] - Selecting on a bool axes supports 2D+ histograms [#398][] - Warnings fixed on NumPy 1.19+ [#404][] [#398]: https://github.com/scikit-hep/boost-histogram/pull/398 [#399]: https://github.com/scikit-hep/boost-histogram/pull/399 [#401]: https://github.com/scikit-hep/boost-histogram/pull/401 [#402]: https://github.com/scikit-hep/boost-histogram/pull/402 [#403]: https://github.com/scikit-hep/boost-histogram/pull/403 [#406]: https://github.com/scikit-hep/boost-histogram/pull/406 ## Version 0.8 ### Version 0.8.0 This version was released just before SciPy 2020 and Boost 1.74. Highlights include better accumulator views, simpler summing, better NumPy and Pandas compatibility, and sums on growing axes. Lots of backend work, including a new wheel building system, internal changes and better reliance on Boost.Histogram's C++ tools for actions like cropping. #### User changes - Weighted histogram cells can now be assigned directly from iterables [#375][] - Weighted views can be summed and added [#368][] - Sum is now identical to the built-in sum function [#365][] - Adding growing axis is better supported [#358][] - Slicing an AxesTuple now keeps the type [#384][] - `ndim` replaces `rank` for NumPy compatibility [#385][] - Any array-like supported in fill [#391][], any iterable can be used for Categories [#392][] - Added Boolean axes, from Boost.Histogram 1.74 [#390][] - Division between histograms is supported [#393][] - More deprecated functionality removed #### Bug fixes - Support older versions of [CloudPickle][] (issue also fixed upstream) [#343][] - Drop extra printout [#338][] - Throw an error instead of returning an incorrect result in more places [#386][] #### Developer changes - Update Boost to 1.73 [#359][], pybind11 to 2.5.0 [#351][], Boost.Histogram to pre-1.74 [#388][] - Cropping no longer uses workaround [#373][] - Many more checks added to [`pre-commit`][] [#366][] - Deprecating `cpp` interface [#391][] - Wheelbuilding migrated to [`cibuildwheel`][] and GHA [#361][] [CloudPickle]: https://github.com/cloudpipe/cloudpickle [`cibuildwheel`]: https://cibuildwheel.readthedocs.io/en/stable [`pre-commit`]: https://pre-commit.com [#338]: https://github.com/scikit-hep/boost-histogram/pull/338 [#340]: https://github.com/scikit-hep/boost-histogram/pull/340 [#343]: https://github.com/scikit-hep/boost-histogram/pull/343 [#351]: https://github.com/scikit-hep/boost-histogram/pull/351 [#358]: https://github.com/scikit-hep/boost-histogram/pull/358 [#359]: https://github.com/scikit-hep/boost-histogram/pull/359 [#361]: https://github.com/scikit-hep/boost-histogram/pull/361 [#365]: https://github.com/scikit-hep/boost-histogram/pull/365 [#366]: https://github.com/scikit-hep/boost-histogram/pull/366 [#368]: https://github.com/scikit-hep/boost-histogram/pull/368 [#373]: https://github.com/scikit-hep/boost-histogram/pull/373 [#375]: https://github.com/scikit-hep/boost-histogram/pull/375 [#384]: https://github.com/scikit-hep/boost-histogram/pull/384 [#385]: https://github.com/scikit-hep/boost-histogram/pull/385 [#386]: https://github.com/scikit-hep/boost-histogram/pull/386 [#388]: https://github.com/scikit-hep/boost-histogram/pull/388 [#390]: https://github.com/scikit-hep/boost-histogram/pull/390 [#391]: https://github.com/scikit-hep/boost-histogram/pull/391 [#392]: https://github.com/scikit-hep/boost-histogram/pull/392 [#393]: https://github.com/scikit-hep/boost-histogram/pull/393 ## Version 0.7 ### Version 0.7.0 This version removes deprecated functionality, and has several backend improvements. The most noticeable user-facing change is the multithreaded fill feature, which can enable significant speedups when you have a dataset that is much larger than the number of bins in your histogram and have free cores to use. Several small bugs have been fixed. #### User changes - Added `threads=` keyword to `.fill` and NumPy functions; 0 for automatic, default is 1 [#325][] - `.metadata` is now settable directly from the AxesTuple [#303][] - Deprecated items from 0.5.x now dropped [#301][] - `cpp` mode updates and fixes [#317][] #### Bug fixes - Dict indexing is now identical to positional indexing, fixes "picking" axes in dict [#320][] - Passing `samples=None` is now always allowed in `.fill` [#325][] #### Developer changes - Build system update, higher requirements for developers (only) [#314][] - Version is now obtained from `setuptools_scm`, no longer stored in repo - Removed `futures` requirement for Python 2 tests - Updated Boost.Histogram, cleaner code with fewer workarounds [#301]: https://github.com/scikit-hep/boost-histogram/pull/301 [#303]: https://github.com/scikit-hep/boost-histogram/pull/303 [#314]: https://github.com/scikit-hep/boost-histogram/pull/314 [#317]: https://github.com/scikit-hep/boost-histogram/pull/317 [#320]: https://github.com/scikit-hep/boost-histogram/pull/320 [#325]: https://github.com/scikit-hep/boost-histogram/pull/325 ## Version 0.6 ### Version 0.6.2 Common analysis tasks are now better supported. Much more complete documentation. Now using development branch of Boost.Histogram again. #### Bug fixes - Fix sum over category axes in indexing [#298][] - Allow single category item selection [#298][] - Allow slicing on axes without flow bins [#288][], [#300][] - Sum repr no longer throws error [#293][] #### Developer changes - Now using scikit-hep/azure-wheel-helpers via subtree [#292][] [#288]: https://github.com/scikit-hep/boost-histogram/pull/288 [#292]: https://github.com/scikit-hep/boost-histogram/pull/292 [#293]: https://github.com/scikit-hep/boost-histogram/pull/293 [#298]: https://github.com/scikit-hep/boost-histogram/pull/298 [#300]: https://github.com/scikit-hep/boost-histogram/pull/300 ### Version 0.6.1 Examples and notebooks are now up to date with the current state of the library. Using Boost 1.72 release. #### User changes - Slices and single values can be mixed in indexing [#279][] - UHI locators supported on axes [#280][] #### Bug fixes - Properties on accumulator views now resolve correctly [#273][] - Division of a histogram by a number is supported again [#278][] - Setting a histogram with length one slice fixed [#279][] - NumPy functions now work with NumPy ints in `bins=` [#282][] - In-place addition avoids a copy [#284][] [#273]: https://github.com/scikit-hep/boost-histogram/pull/273 [#278]: https://github.com/scikit-hep/boost-histogram/pull/278 [#279]: https://github.com/scikit-hep/boost-histogram/pull/279 [#280]: https://github.com/scikit-hep/boost-histogram/pull/280 [#282]: https://github.com/scikit-hep/boost-histogram/pull/282 [#284]: https://github.com/scikit-hep/boost-histogram/pull/284 ### Version 0.6.0 This version fills out most of the remaining features missing from the 0.5.x series. You can now use all the storages without the original caveats; even the accumulators can be accessed array-at-a-time without copy, pickled quickly, and set array-at-a-time, as well. The API has changed considerably, providing a more consistent experience in Python. Most of the classic API still works in this release, but will issue a warning and will be removed from the next release. Please use this release to transition existing 0.5.x code to the new API. #### User changes - Histogram and Axis classes now follow PEP 8 naming scheme (`histogram`->`Histogram`, `regular`->`Regular`, `int`->`Int64` etc.) [#192][], [#255][] - You can now view a histogram with accumulators, with property access such as `h.view().value` [#194][] - Circular variable and integer axes added [#231][] - Split Category into `StrCategory` and `IntCategory`, now allows empty categories when `growth=True` [#221][] - `StrCategory` fills are safer and faster [#239][], [#244][] - Added axes transforms [#192][] - `Function(forward, inverse)` transform added, allowing ultra-fast C function pointer transforms [#231][] - You can now set histogram contents directly [#250][] - You can now sum over a range with endpoints [#185][] - `h.axes` now has the functions from axis as well. [#183][] - `bh.project` has become `bh.sum` [#185][] - `.reduce(...)` and the reducers in `bh.algorithm` have been removed in favor of dictionary based UHI slicing [#259][] - `bh.numpy` module interface updates, `histogram=bh.Histogram` replaces cryptic `bh=True`, and `density=True` is now supported in NumPy mode [#256][] - Added `hist.copy()` [#218][] and `hist.shape` [#264][] - Signatures are much nicer in Python 3 [#188][] - Reprs are better, various properties like `__module__` are now set correctly [#200][] #### Bug fixes - Unlimited and AtomicInt storages now allow single item access [#194][] - `.view()` now no longer makes a copy [#194][] - Fixes related to string category axis fills [#233][], [#230][] - Axes are no longer copies, support setting metadata [#238][], [#246][] - Pickling accumulator storages is now comparable in performance simple storages [#258][] #### Developer changes - The linux wheels are now 10-20x smaller [#229][] - The hist/axis classes are now pure Python, with a C++ object inside [#183][] - Most internal names changed, `core->_core`, etc. [#183][] - The `uhi` module is now `tag`. [#183][] - `boost_histogram.cpp as bh` provides C++ high-compatibility mode. [#183][] - Indexing tags now use full UHI instead of workarounds [#185][] - Removed log and sqrt special axes types[#231][] - Family and registration added, new casting system [#200][] [#183]: https://github.com/scikit-hep/boost-histogram/pull/183 [#185]: https://github.com/scikit-hep/boost-histogram/pull/185 [#188]: https://github.com/scikit-hep/boost-histogram/pull/188 [#192]: https://github.com/scikit-hep/boost-histogram/pull/192 [#194]: https://github.com/scikit-hep/boost-histogram/pull/194 [#200]: https://github.com/scikit-hep/boost-histogram/pull/200 [#218]: https://github.com/scikit-hep/boost-histogram/pull/218 [#221]: https://github.com/scikit-hep/boost-histogram/pull/221 [#229]: https://github.com/scikit-hep/boost-histogram/pull/229 [#230]: https://github.com/scikit-hep/boost-histogram/pull/230 [#231]: https://github.com/scikit-hep/boost-histogram/pull/231 [#233]: https://github.com/scikit-hep/boost-histogram/pull/233 [#238]: https://github.com/scikit-hep/boost-histogram/pull/238 [#239]: https://github.com/scikit-hep/boost-histogram/pull/239 [#244]: https://github.com/scikit-hep/boost-histogram/pull/244 [#246]: https://github.com/scikit-hep/boost-histogram/pull/246 [#250]: https://github.com/scikit-hep/boost-histogram/pull/250 [#255]: https://github.com/scikit-hep/boost-histogram/pull/255 [#256]: https://github.com/scikit-hep/boost-histogram/pull/256 [#258]: https://github.com/scikit-hep/boost-histogram/pull/258 [#259]: https://github.com/scikit-hep/boost-histogram/pull/259 [#264]: https://github.com/scikit-hep/boost-histogram/pull/264 ## Version 0.5 ### Version 0.5.2 #### User changes: - `bh.loc` supports an offset [#164][] - Nicer reprs in several places [#167][] - Deprecate `.at` and `.axis` [#170][] #### Bug fixes: - Use relative paths in setup.py to avoid resolving WSL paths on Windows [#162][], [#163][] - Better pybind11 support for Python 3.8 [#168][] #### Developer changes: - Serialization code shared with Boost.Histogram [#166][] - Avoid unused PEP 517 isolation for now [#171][] (may return with proper PEP 518 support eventually) [#162]: https://github.com/scikit-hep/boost-histogram/pull/162 [#163]: https://github.com/scikit-hep/boost-histogram/pull/163 [#164]: https://github.com/scikit-hep/boost-histogram/pull/164 [#166]: https://github.com/scikit-hep/boost-histogram/pull/166 [#167]: https://github.com/scikit-hep/boost-histogram/pull/167 [#168]: https://github.com/scikit-hep/boost-histogram/pull/168 [#170]: https://github.com/scikit-hep/boost-histogram/pull/170 [#171]: https://github.com/scikit-hep/boost-histogram/pull/171 ### Version 0.5.1 #### User changes: - Removed the `bh.indexed`/`h.indexed` iterator [#150][] - Added `.axes` AxisTuple, with direct access to properties [#150][] - Cleaned up tab completion in IPython [#150][] #### Bug fixes: - Fixed a bug in the sdist missing Boost.Variant2 [#154][] - Fixed filling on strided inputs [#158][] [#150]: https://github.com/scikit-hep/boost-histogram/pull/150 [#154]: https://github.com/scikit-hep/boost-histogram/pull/154 [#158]: https://github.com/scikit-hep/boost-histogram/pull/158 [#159]: https://github.com/scikit-hep/boost-histogram/pull/159 ### Version 0.5.0 First beta release and beginning of the changelog. #### Known issues: - Unlimited storage does not support pickling or classic multiprocessing - Some non-simple storages do not support some forms of access, like `.view` - Indexing and the array versions (such as centers) are incomplete and subject to change - The numpy module is provisional and subject to change - Docstrings and signatures will improve in later versions (especially on Python 3) scikit-hep-boost-histogram-5f6f3e3/docs/conf.py000066400000000000000000000102701516627434300215560ustar00rootroot00000000000000# To regenerate API docs: # nox -s build_api_docs from __future__ import annotations # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import contextlib import importlib.metadata import shutil import sys from pathlib import Path # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- DIR = Path(__file__).parent BASEDIR = DIR.parent sys.path.append(str(BASEDIR / "src")) # -- Project information ----------------------------------------------------- project = "boost_histogram" copyright = "2020, Henry Schreiner, Hans Dembinski" author = "Henry Schreiner, Hans Dembinski" with contextlib.suppress(ModuleNotFoundError): version = importlib.metadata.version("boost_histogram") # passed if no version (latest/git hash) # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "myst_parser", "nbsphinx", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.mathjax", "sphinx.ext.napoleon", "sphinx_copybutton", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [ "_build", "**.ipynb_checkpoints", "Thumbs.db", ".DS_Store", ".env", "api/boost_histogram.*.rst", ] # Read the Docs needs this explicitly listed. master_doc = "index" # Intersphinx setup intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "numpy": ("https://numpy.org/doc/stable/", None), } # -- Options for Notebook input ---------------------------------------------- html_logo = "_images/BoostHistogramPythonLogo.png" html_title = "boost-histogram docs" nbsphinx_execute = "never" # Can change to auto highlight_language = "python3" nbsphinx_execute_arguments = [ "--InlineBackend.figure_formats={'png2x'}", "--InlineBackend.rc={'figure.dpi': 96}", ] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_book_theme" # Config for the Sphinx book html_baseurl = "https://boost-histogram.readthedocs.io/en/latest/" html_theme_options = { "home_page_in_toc": True, "repository_url": "https://github.com/scikit-hep/boost-histogram", "use_repository_button": True, "use_issues_button": True, "use_edit_page_button": True, } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # _static # Simpler docs (no build required) autodoc_mock_imports = ["boost_histogram._core"] def prepare(app): outer_nb = BASEDIR / "notebooks" inner_nb = DIR / "notebooks" notebooks = outer_nb.glob("*.ipynb") for notebook in notebooks: shutil.copy(notebook, inner_nb / notebook.name) outer_cont = BASEDIR / ".github" inner_cont = DIR contributing = "CONTRIBUTING.md" shutil.copy(outer_cont / contributing, inner_cont / "contributing.md") def clean_up(app, exception): inner_nb = DIR / "notebooks" for notebook in inner_nb.glob("*.ipynb"): notebook.unlink() inner_cont = DIR (inner_cont / "contributing.md").unlink() def setup(app): # Copy the notebooks in app.connect("builder-inited", prepare) # Clean up the generated notebooks app.connect("build-finished", clean_up) scikit-hep-boost-histogram-5f6f3e3/docs/index.rst000066400000000000000000000105231516627434300221210ustar00rootroot00000000000000.. boost-histogram documentation master file, created by sphinx-quickstart on Tue Apr 23 12:12:27 2019. You can adapt this file completely to your liking, but it should at least contain the root ``toctree`` directive. .. image:: _images/BoostHistogramPythonLogo.png :width: 70% :alt: Boost histogram logo :align: center Welcome to boost-histogram's documentation! =========================================== |Actions Status| |Documentation Status| |PyPI version| |Conda-Forge| |PyPI platforms| |DOI| |GitHub Discussion| |Gitter| |Scikit-HEP| Boost-histogram (`source `__) is a Python package providing Python bindings for Boost.Histogram_ (`source `__). You can install this library from `PyPI `__ with pip or you can use Conda via `conda-forge `__: .. code-block:: bash python -m pip install boost-histogram .. code-block:: bash conda install -c conda-forge boost-histogram All the normal best-practices for Python apply; you should be in a virtual environment, etc. See :ref:`usage-installation` for more details. An example of usage: .. code-block:: python3 import boost_histogram as bh # Compose axis however you like; this is a 2D histogram hist = bh.Histogram(bh.axis.Regular(2, 0, 1), bh.axis.Regular(4, 0.0, 1.0)) # Filling can be done with arrays, one per dimension hist.fill([0.3, 0.5, 0.2], [0.1, 0.4, 0.9]) # NumPy array view into histogram counts, no overflow bins counts = hist.view() See :ref:`usage-quickstart` for more. .. toctree:: :maxdepth: 1 :caption: User Guide: user-guide/installation user-guide/quickstart user-guide/histogram user-guide/axes user-guide/storage user-guide/accumulators user-guide/transforms user-guide/indexing user-guide/plotting user-guide/analyses user-guide/numpy user-guide/subclassing user-guide/comparison .. toctree:: :maxdepth: 1 :caption: Examples: notebooks/SimpleExample notebooks/aghast notebooks/ThreadedFills notebooks/PerformanceComparison notebooks/xarray notebooks/BoostHistogramHandsOn .. toctree:: :maxdepth: 2 :titlesonly: :caption: Developers :glob: contributing support changelog .. toctree:: :caption: API Reference: api/modules Acknowledgements ---------------- This library was primarily developed by Henry Schreiner and Hans Dembinski. Support for this work was provided by the National Science Foundation cooperative agreement OAC-1836650 (IRIS-HEP) and OAC-1450377 (DIANA/HEP). Any opinions, findings, conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Science Foundation. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _Boost.Histogram: https://www.boost.org/doc/libs/release/libs/histogram/doc/html/index.html .. |Actions Status| image:: https://github.com/scikit-hep/boost-histogram/workflows/Tests/badge.svg :target: https://github.com/scikit-hep/boost-histogram/actions .. |Documentation Status| image:: https://readthedocs.org/projects/boost-histogram/badge/?version=latest :target: https://boost-histogram.readthedocs.io/en/latest/?badge=latest .. |PyPI version| image:: https://badge.fury.io/py/boost-histogram.svg :target: https://pypi.org/project/boost-histogram .. |Conda-Forge| image:: https://img.shields.io/conda/vn/conda-forge/boost-histogram :target: https://github.com/conda-forge/boost-histogram-feedstock .. |PyPI platforms| image:: https://img.shields.io/pypi/pyversions/boost-histogram :target: https://pypi.org/project/boost-histogram/ .. |DOI| image:: https://zenodo.org/badge/148885351.svg :target: https://zenodo.org/badge/latestdoi/148885351 .. |GitHub Discussion| image:: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github :target: https://github.com/scikit-hep/boost-histogram/discussions .. |Gitter| image:: https://badges.gitter.im/HSF/PyHEP-histogramming.svg :target: https://gitter.im/HSF/PyHEP-histogramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge .. |Scikit-HEP| image:: https://scikit-hep.org/assets/images/Scikit--HEP-Project-blue.svg :target: https://scikit-hep.org/ scikit-hep-boost-histogram-5f6f3e3/docs/make.bat000066400000000000000000000013601516627434300216640ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd scikit-hep-boost-histogram-5f6f3e3/docs/notebooks/000077500000000000000000000000001516627434300222625ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/notebooks/conf.sh000066400000000000000000000001301516627434300235350ustar00rootroot00000000000000--InlineBackend.figure_formats="{'svg', 'pdf'}" --InlineBackend.rc="{'figure.dpi': 96}" scikit-hep-boost-histogram-5f6f3e3/docs/support.rst000066400000000000000000000014061516627434300225260ustar00rootroot00000000000000Support ================ If you are stuck with a problem using Boost-histogram, please do get in touch at our `Issues `_ or `Gitter Channel `_. The developers are willing to help. You can save time by following this procedure when reporting a problem: * Do try to solve the problem on your own first. Read the documentation, including using the search feature, index and reference documentation. * Search the issue archives to see if someone else already had the same problem. * Before writing, try to create a minimal example that reproduces the problem. You'll get the fastest response if you can send just a handful of lines of code that show what isn't working. scikit-hep-boost-histogram-5f6f3e3/docs/template/000077500000000000000000000000001516627434300220725ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/template/module.rst_t000066400000000000000000000003031516627434300244300ustar00rootroot00000000000000{%- if show_headings %} {{- [basename, "module"] | join(' ') | e | heading }} {% endif -%} .. automodule:: {{ qualname }} {%- for option in automodule_options %} :{{ option }}: {%- endfor %} scikit-hep-boost-histogram-5f6f3e3/docs/template/package.rst_t000066400000000000000000000017251516627434300245470ustar00rootroot00000000000000{%- macro automodule(modname, options) -%} .. automodule:: {{ modname }} {%- for option in options %} :{{ option }}: {%- endfor %} {%- endmacro %} {%- macro toctree(docnames) -%} {% for docname in docnames %} .. include:: {{ docname }}.rst {%- endfor %} {%- endmacro %} {%- if is_namespace %} {{- [pkgname, "namespace"] | join(" ") | e | heading }} {% else %} {{- pkgname | e | heading }} {% endif %} {%- if modulefirst and not is_namespace %} {{ automodule(pkgname, automodule_options) }} {% endif %} {%- if subpackages %} {{ toctree(subpackages) }} {% endif %} {%- if submodules %} {% if separatemodules %} {{ toctree(submodules) }} {%- else %} {%- for submodule in submodules %} {% if show_headings %} {{- submodule | e | heading }} {% endif %} {{ automodule(submodule, automodule_options) }} {% endfor %} {%- endif %} {% endif %} {%- if not modulefirst and not is_namespace %} Module contents --------------- {{ automodule(pkgname, automodule_options) }} {% endif %} scikit-hep-boost-histogram-5f6f3e3/docs/template/toc.rst_t000066400000000000000000000001501516627434300237300ustar00rootroot00000000000000 .. toctree:: :maxdepth: {{ maxdepth }} {% for docname in docnames %} {{ docname }} {%- endfor %} scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/000077500000000000000000000000001516627434300223305ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/accumulators.rst000066400000000000000000000130521516627434300255650ustar00rootroot00000000000000.. _usage-accumulators: Accumulators ============ Common properties ----------------- All accumulators can be filled like a histogram. You just call ``.fill`` with values, and this looks and behaves like filling a single-bin or "scalar" histogram. Like histograms, the fill is inplace. All accumulators have a ``.value`` property as well, which gives the primary value being accumulated. Types ----- There are several accumulators. Sum ^^^ This is the simplest accumulator, and is never returned from a histogram. This is internally used by the Double and Unlimited storages to perform sums when needed. It uses a highly accurate Neumaier sum to compute the floating point sum with a correction term. Since this accumulator is never returned by a histogram, it is not available in a view form, but only as a single accumulator for comparison and access to the algorithm. Usage example in Python 3.8, showing how non-accurate sums fail to produce the obvious answer, 2.0:: import math import numpy as np import boost_histogram as bh values = [1.0, 1e100, 1.0, -1e100] print(f"{sum(values) = } (simple)") print(f"{math.fsum(values) = }") print(f"{np.sum(values) = } (pairwise)") print(f"{bh.accumulators.Sum().fill(values) = }") .. code-block:: text sum(values) = 0.0 (simple) math.fsum(values) = 2.0 np.sum(values) = 0.0 (pairwise) bh.accumulators.Sum().fill(values) = Sum(0 + 2) Note that this is still intended for performance and does not guarantee correctness as ``math.fsum`` does. In general, you must not have more than two orders of values:: values = [1., 1e100, 1e50, 1., -1e50, -1e100] print(f"{math.fsum(values) = }") print(f"{bh.accumulators.Sum().fill(values) = }") .. code-block:: text math.fsum(values) = 2.0 bh.accumulators.Sum().fill(values) = Sum(0 + 0) You should note that this is a highly contrived example and the Sum accumulator should still outperform simple and pairwise summation methods for a minimal performance cost. Most notably, you have to have large cancellations with negative values, which histograms generally do not have. You can use ``+=`` with a float value or a Sum to fill as well. WeightedSum ^^^^^^^^^^^ This accumulator is contained in the Weight storage, and supports Views. It provides two values; ``.value``, and ``.variance``. The value is the sum of the weights, and the variance is the sum of the squared weights. For example, you could sum the following values:: import boost_histogram as bh values = [10]*10 smooth = bh.accumulators.WeightedSum().fill(values) print(f"{smooth = }") values = [1]*9 + [91] rough = bh.accumulators.WeightedSum().fill(values) print(f"{rough = }") .. code-block:: text smooth = WeightedSum(value=100, variance=1000) rough = WeightedSum(value=100, variance=8290) When filling, you can optionally provide a ``variance=`` keyword, with either a single value or a matching length array of values. You can also fill with ``+=`` on a value or another WeighedSum. Mean ^^^^ This accumulator is contained in the Mean storage, and supports Views. It provides three values; ``.count``, ``.value``, and ``.variance``. Internally, the variance is stored as ``_sum_of_deltas_squared``, which is used to compute ``variance``. For example, you could compute the mean of the following values:: import boost_histogram as bh values = [10]*10 smooth = bh.accumulators.Mean().fill(values) print(f"{smooth = }") values = [1]*9 + [91] rough = bh.accumulators.Mean().fill(values) print(f"{rough = }") .. code-block:: text smooth = Mean(count=10, value=10, variance=0) rough = Mean(count=10, value=10, variance=810) You can add a ``weight=`` keyword when filling, with either a single value or a matching length array of values. You can call a Mean with a value or with another Mean to fill inplace, as well. WeightedMean ^^^^^^^^^^^^ This accumulator is contained in the WeightedMean storage, and supports Views. It provides four values; ``.sum_of_weights``, ``sum_of_weights_squared``, ``.value``, and ``.variance``. Internally, the variance is stored as ``_sum_of_weighted_deltas_squared``, which is used to compute ``variance``. For example, you could compute the mean of the following values:: import boost_histogram as bh values = [1]*9 + [91] wm = bh.accumulators.WeightedMean().fill(values, weight=2) print(f"{wm = }") .. code-block:: text wm = WeightedMean(sum_of_weights=20, sum_of_weights_squared=40, value=10, variance=810) You can add a ``weight=`` keyword when filling, with either a single value or a matching length array of values. You can call a WeightedMean with a value or with another WeightedMean to fill inplace, as well. Views ----- Most of the accumulators (except Sum) support a View. This is what is returned from a histogram when ``.view()`` is requested. This is a structured NumPy ndarray, with a few small additions to make them easier to work with. Like a NumPy recarray, you can access the fields with attributes; you can even access (but not set) computed attributes like ``.variance``. A view will also return an accumulator instance if you select a single item. You can set a view's contents with a stacked array, and each item in the stack will be used for the (computed) values that a normal constructor would take. For example, WeighedMean can take an array with a final dimension four long, with ``sum_of_weights``, ``sum_of_weights_squared``, ``value``, and ``variance`` elements, even though several of these values are computed from the internal representation. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/analyses.rst000066400000000000000000000024531516627434300247050ustar00rootroot00000000000000.. _usage-analyses: Analyses examples ================= Bool and category axes ---------------------- Taken together, the flexibility in axes and the tools to easily sum over axes can be applied to transform the way you approach analysis with histograms. For example, let’s say you are presented with the following data in a 3xN table: ============== ======================== Data Details ============== ======================== ``value`` ``is_valid`` True or False ``run_number`` A collection of integers ============== ======================== In a traditional analysis, you might bin over ``value`` where ``is_valid`` is True, and then make a collection of histograms, one for each run number. With boost-histogram, you can make a single histogram, and use an axis for each: .. code-block:: python3 value_ax = bh.axis.Regular(100, -5, 5) bool_ax = bh.axis.Integer(0, 2, underflow=False, overflow=False) run_number_ax = bh.axis.IntCategory([], growth=True) Now, you can use these axes to create a single histogram that you can fill. If you want to get a histogram of all run numbers and just the True ``is_valid`` selection, you can use a ``sum``: .. code-block:: python3 h1 = hist[:, True, sum] You can expand this example to any number of dimensions, boolean flags, and categories. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/axes.rst000066400000000000000000000113171516627434300240250ustar00rootroot00000000000000.. _usage-axes: Axes ==== In boost-histogram, a histogram is collection of Axis objects and a storage. Axis types ---------- There are several axis types to choose from. Regular axis ^^^^^^^^^^^^ .. image:: ../_images/axis_regular.png :alt: Regular axis illustration :align: center .. py:function:: bh.axis.Regular(bins, start, stop, *, metadata="", underflow=True, overflow=True, circular=False, growth=False, transform=None) :noindex: The regular axis can have overflow and/or underflow bins (enabled by default). It can also grow if ``growth=True`` is given. In general, you should not mix options, as growing axis will already have the correct flow bin settings. The exception is ``underflow=False, overflow=False``, which is quite useful together to make an axis with no flow bins at all. There are some other useful axis types based on regular axis: .. image:: ../_images/axis_circular.png :alt: Regular axis illustration :align: center .. py:function:: bh.axis.Regular(..., circular=True) :noindex: This wraps around, so that out-of-range values map back into the valid range circularly. Regular axis: Transforms ^^^^^^^^^^^^^^^^^^^^^^^^ Regular axes support transforms, as well; these are functions that convert from an external, non-regular bin spacing to an internal, regularly spaced one. A transform is made of two functions, a ``forward`` function, which converts external to internal (and for which the transform is usually named), and a ``inverse`` function, which converts from the internal space back to the external space. If you know the functional form of your spacing, you can get the benefits of a constant performance scaling just like you would with a normal regular axis, rather than falling back to a variable axis and a poorer scaling from the bin edge lookup required there. You can define your own functions for transforms, see :ref:`usage-transforms`. If you use compiled/numba functions, you can keep the high performance you would expect from a Regular axis. There are also several precompiled transforms: .. py:function:: bh.axis.Regular(..., transform=bh.axis.transform.sqrt) :noindex: This is an axis with bins transformed by a sqrt. .. py:function:: bh.axis.Regular(..., transform=bh.axis.transform.log) :noindex: Transformed by log. .. py:function:: bh.axis.Regular(..., transform=bh.axis.transform.Power(v)) :noindex: Transformed by a power (the argument is the power). Variable axis ^^^^^^^^^^^^^ .. image:: ../_images/axis_variable.png :alt: Regular axis illustration :align: center .. py:function:: bh.axis.Variable([edge1, ...], *, metadata="", underflow=True, overflow=True, circular=False, growth=False) :noindex: You can set the bin edges explicitly with a variable axis. The options are mostly the same as the Regular axis. Integer axis ^^^^^^^^^^^^ .. image:: ../_images/axis_integer.png :alt: Regular axis illustration :align: center .. py:function:: bh.axis.Integer(start, stop, *, metadata="", underflow=True, overflow=True, circular=False, growth=False) :noindex: This could be mimicked with a regular axis, but is simpler and slightly faster. Bins are whole integers only, so there is no need to specify the number of bins. One common use for an integer axis could be a true/false axis: .. code-block:: python3 bool_axis = bh.axis.Integer(0, 2, underflow=False, overflow=False) Another could be for an IntEnum if the values are contiguous. Category axis ------------- .. image:: ../_images/axis_category.png :alt: Regular axis illustration :align: center .. py:function:: bh.axis.IntCategory([value1, ...], metadata="", growth=False) :noindex: You should put integers in a category axis; but unlike an integer axis, the integers do not need to be adjacent. One use for an IntCategory axis is for an IntEnum: .. code-block:: python3 import enum class MyEnum(enum.IntEnum): a = 1 b = 5 my_enum_axis = bh.axis.IntEnum(list(MyEnum), underflow=False, overflow=False) .. py:function:: bh.axis.StrCategory([str1, ...], metadata="", growth=False) :noindex: You can put strings in a category axis as well. The fill method supports lists or arrays of strings to allow this to be filled. Boolean axis ^^^^^^^^^^^^ .. py:function:: bh.axis.Boolean(metadata="") :noindex: This is an axis that holds either ``True`` and ``False`` only. Manipulating Axes ----------------- Axes have a variety of methods and properties that are useful. When inside a histogram, you can also access these directly on the ``hist.axes`` object, and they return a tuple of valid results. If the property or method normally returns an array, the ``axes`` version returns a broadcasting-ready version in the output tuple. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/comparison.rst000066400000000000000000000054351516627434300252430ustar00rootroot00000000000000.. _usage-comparison: Comparison with Boost.Histogram =============================== ``boost-histogram`` was based on the C++ library Boost.Histogram. In most ways, it mimics the spirit of this library; if you learn to use one, you probably can use the other. There are a few differences, however, mostly around adhering to Python conventions: Removals ^^^^^^^^ There are a few parts of the Boost.Histogram interface that are not bound. They are: The call operator This is provided in C++ to allow single item filling, and was designed to mimic the accumulator syntax used elsewhere in Boost. It also works nicely with some STL algorithms. It was not provided in Python because using call to modify an object is not common in Python, using call makes duck-typing more dangerous, and single-item fills are not encouraged in Python due to poor performance. The ``.fill`` method from Boost.Histogram 1.72 is bound, however - this provides fast fills without the drawbacks. If you want to fill with a single item, Python's ``.fill`` does support single item fills. Histogram make functions These functions, such as ``make_histogram`` and ``make_weighted_histogram``, are provided in Boost.Histogram to make the template syntax easier in C++14. In C++17, they are replaced by directly using the ``histogram`` constructor; the Python bindings are not limited by old templating syntax, and choose to only provide the newer spelling. Custom components Many components in Boost.Histogram are configurable or replaceable at compile time; since Python code is precompiled, a comprehensive but static subset was selected for the Python bindings. Changes ^^^^^^^ Naming The bindings follow modern Python conventions, with CamelCase for classes, etc. The Boost.Histogram library follows Boost conventions. Serialization The Python bindings use a pickle-based binary serialization, so cannot read files saved in C++ using Boost.Serialize. Properties Many methods in C++ are properties in Python. ``.axis(i)`` is replaced with ``.axes[i]``. Indexing The Python bindings use standard Python indexing for selection and setting. You can recover the functionality of ``.at(i)`` at endpoints with ``bh.tag.at(i)``. Renames The ``.rank()`` method is replaced by the ``.ndim`` property to match the common NumPy spelling. Additions ^^^^^^^^^ Unified Histogram Indexing The Python bindings support UHI, a proposal to unify and simplify histogram indexing in Python. Custom transforms Custom transforms are possible using Numba or a C pointer. In Boost.Histogram, you can use templating to make arbitrary transforms, so a run time transform is not as necessary (but may be added). NumPy compatibility The Python bindings do several things to simplify NumPy compatibility. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/histogram.rst000066400000000000000000000126201516627434300250600ustar00rootroot00000000000000.. _usage-histogram: Histogram ========= The Histogram object is the core of boost-histogram. Filling ^^^^^^^ You call ``.fill`` to fill. You must have one 1D array (or scalar value) per dimension. For maximum performance, numeric arrays should be continuously laid out in memory, and either 64-bit floats or ints. If any other layouts or numeric datatypes are supplied, a temporary copy will be made internally before filling. All storages support a ``weight=`` parameter, and some storages support a ``sample=`` parameter. If supplied, they must be a scalar (applies to all items equally) or an iterable of scalars/1D arrays that matches the number of dimensions of the histogram. The summing accumulators (not ``Mean()`` and ``WeightedMean())``) support threaded filling. Pass ``threads=N`` to the fill parameter to fill with ``N`` threads (and using 0 will select the number of virtual cores on your system). This is helpful only if you have a large number of entries compared to your number of bins, as all non-atomic storages will make copies for each thread, and then will recombine after the fill is complete. Data ^^^^ The primary values from a histogram are always available as ``.values()``. The variances are available as ``.variances()``, unless you fill an unweighed histogram with weights, which will cause this to return None, since the variances are no longer computable (use a weighted storage instead if you need the variances). The counts are available as ``.counts()``. If the histogram is weighted, ``.counts()`` returns the effective counts; see `UHI `_ for details. Views ^^^^^ While Histograms do conform to the Python buffer protocol, the best way to get access to the raw contents of a histogram as a NumPy array is with ``.view()``. This way you can optionally pass ``flow=True`` to get the flow bins, and if you have an accumulator storage, you will get a View, which is a slightly augmented ndarrray subclass (see :ref:`usage-accumulators`). Views support setting as well for non-computed properties; you can use an expression like this to set the values of an accumulator storage: .. code-block:: python3 h.view().value = values You can also used stacked arrays (N+1 dimensional) to set a histogram's contents. This is especially useful if you need to set a computed value, like variance on a Mean/WeightedMean storage, which cannot be set using the above method: .. code-block:: python3 h[...] = np.stack([values, variances], axis=-1) If you leave endpoints off (such as with ``...`` above), then you can match the size with or without flow bins. Operations ^^^^^^^^^^ * ``h.rank``: The number of dimensions * ``h.size or len(h)``: The number of bins * ``+``: Add two histograms, or add a scalar or array (storages must match types currently) * ``*=``: Multiply by a scaler, array, or histogram (not all storages) (``hist * scalar`` and ``scalar * hist`` supported too) * ``/=``: Divide by a scaler, array, or histogram (not all storages) (``hist / scalar`` supported too) * ``[...]``: Access a bin or a range of bins (get or set) (see :ref:`usage-indexing`) * ``.sum(flow=False)``: The total count of all bins * ``.project(ax1, ax2, ...)``: Project down to listed axis (numbers) * ``.to_numpy(flow=False, view=False)``: Convert to a NumPy style tuple (with or without under/overflow bins, and either return values (the default) or the entire view for accumulator storages.) * ``.view(flow=False)``: Get a view on the bin contents (with or without under/overflow bins) * ``.values(flow=False)``: Get a view on the values (counts or means, depending on storage) * ``.variances(flow=False)``: Get the variances if available * ``.counts(flow=False)``: Get the effective counts for all storage types * ``.reset()``: Set counters to 0 * ``.empty(flow=False)``: Check to see if the histogram is empty (can check flow bins too if asked) * ``.copy(deep=False)``: Make a copy of a histogram * ``.axes``: Get the axes as a tuple-like (all properties of axes are available too) * ``.axes[0]``: Get the 0th axis * ``.axes.edges``: The lower values as a broadcasting-ready array * ``.axes.centers``: The centers of the bins broadcasting-ready array * ``.axes.widths``: The bin widths as a broadcasting-ready array * ``.axes.metadata``: A tuple of the axes metadata * ``.axes.traits``: A tuple of the axes traits * ``.axes.size``: A tuple of the axes sizes (size without flow) * ``.axes.extent``: A tuple of the axes extents (size with flow) * ``.axes.bin(*args)``: Returns the bin edges as a tuple of pairs (continuous axis) or values (describe) * ``.axes.index(*args)``: Returns the bin index at a value for each axis * ``.axes.value(*args)``: Returns the bin value at an index for each axis Saving a Histogram ^^^^^^^^^^^^^^^^^^ You can save a histogram using pickle: .. code-block:: python3 import pickle with open("file.pkl", "wb") as f: pickle.dump(h, f) with open("file.pkl", "rb") as f: h2 = pickle.load(f) assert h == h2 Special care was taken to ensure that this is fast and efficient. Please use the latest version of the Pickle protocol you feel comfortable using; you cannot use version 0, the version that used to be default on Python 2. The most recent versions provide performance benefits. You can nest this in other Python structures, like dictionaries, and save those instead. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/indexing.rst000066400000000000000000000023401516627434300246660ustar00rootroot00000000000000.. _usage-indexing: Indexing ======== Boost-histogram implements the UHI indexing protocol. You can read more about it on the `UHI Indexing `_ page. Boost-histogram specific details -------------------------------- Boost-histogram implements ``bh.loc``, ``builtins.sum``, ``bh.rebin``, ``bh.underflow``, and ``bh.overflow`` from the UHI spec. A ``bh.tag.at`` locator is provided as well, which simulates the Boost.Histogram C++ ``.at()`` indexing using the UHI locator protocol. Boost-histogram allows "picking" using lists, similar to NumPy. If you select with multiple lists, boost-histogram instead selects per-axis, rather than group-selecting and reducing to a single axis, like NumPy does. You can use ``bh.loc(...)`` inside these lists. Example:: h = bh.histogram( bh.axis.Regular(10, 0, 1), bh.axis.StrCategory(["a", "b", "c"]), bh.axis.IntCategory([5, 6, 7]), ) minihist = h[:, [bh.loc("a"), bh.loc("c")], [0, 2]] # Produces a 3D histgoram with Regular(10, 0, 1) x StrCategory(["a", "c"]) x IntCategory([5, 7]) This feature is considered experimental in boost-histogram 1.1.0. Removed bins are not added to the overflow bin currently. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/installation.rst000066400000000000000000000041561516627434300255710ustar00rootroot00000000000000.. _usage-installation: Installation ============ Boost-histogram (`source `__) is a Python package providing Python bindings for Boost.Histogram_ (`source `__). .. _Boost.Histogram: https://www.boost.org/doc/libs/release/libs/histogram/doc/html/index.html You can install this library from `PyPI `__ with pip: .. code-block:: bash python -m pip install boost-histogram or you can use Conda through `conda-forge `__: .. code-block:: bash conda install -c conda-forge boost-histogram All the normal best-practices for Python apply; you should be in a virtual environment, etc. If you built from source, you can quickly verify the build with: .. code-block:: bash python -m boost_histogram.test Or, from Python: .. code-block:: python import unittest assert unittest.main("boost_histogram.test", warnings="error", exit=False).result == 0 Supported platforms ------------------- Binaries available: ^^^^^^^^^^^^^^^^^^^ The supported platforms are listed in the README - All common linux machines, all common macOS versions, and all common Windows versions. Conda-Forge ^^^^^^^^^^^ The boost-histogram package is available on Conda-Forge, as well. All supported versions are available. :: conda install -c conda-forge boost-histogram Source builds ^^^^^^^^^^^^^ For a source build, for example from an “sdistâ€� package, the only requirements are a C++14 compatible compiler. The compiler requirements are dictated by Boost.Histogram’s C++ requirements: gcc >= 5.5, clang >= 3.8, msvc >= 14.1. You should have a version of pip less than 2-3 years old (10+). NumPy is downloaded during the build (enables multithreaded builds). Boost is not required or needed (this only depends on included header-only dependencies).This library is under active development; you can install directly from GitHub if you would like. .. code-block:: bash python -m pip install git+https://github.com/scikit-hep/boost-histogram.git@develop scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/numpy.rst000066400000000000000000000076741516627434300242500ustar00rootroot00000000000000.. _usage-numpy: NumPy compatibility =================== Histogram conversion -------------------- Accessing the storage array ^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can access the storage of any Histogram using ``.view()``, see :ref:`usage-histogram`. NumPy tuple output ^^^^^^^^^^^^^^^^^^ You can directly convert a histogram into the tuple of outputs that ``np.histogram*`` would give you using ``.to_numpy()`` or ``.to_numpy(flow=True)`` on any histogram. This returns ``edges[0], edges[1], ..., values``, and the edges are NumPy-style (upper edge inclusive). NumPy adaptors -------------- You can use boost-histogram as a drop in replacement for NumPy histograms. All three histogram functions (``bh.numpy.histogram``, ``bh.numpy.histgoram2d``, and ``bh.numpy.histogramdd``) are provided. The syntax is identical, though boost-histogram adds three new keyword-only arguments; ``storage=`` to select the storage, ``histogram=bh.Histogram`` to produce a boost-histogram instead of a tuple, and ``threads=N`` to select a number of threads to fill with. 1D histogram example ^^^^^^^^^^^^^^^^^^^^ If you try the following in an IPython session, you will get: .. code-block:: python3 import numpy as np import boost_histogram as bh norm_vals = np.concatenate( [ np.random.normal(loc=5, scale=1, size=1_000_000), np.random.normal(loc=2, scale=0.2, size=200_000), np.random.normal(loc=8, scale=0.2, size=200_000), ] ) .. code-block:: ipython %%timeit bins, edges = np.histogram(norm_vals, bins=100, range=(0, 10)) .. code-block:: text 17.4 ms ± 2.64 ms per loop (mean ± std. dev. of 7 runs, 100 loops each) Of course, you then are either left on your own to compute centers, density, widths, and more, or in some cases you can change the computation call itself to add ``density=``, or use the matching function inside Matplotlib, and the API is different if you want 2D or ND histograms. But if you already use NumPy histograms and you really don’t want to rewrite your code, boost-histogram has adaptors for the three histogram functions in NumPy: .. code-block:: ipython %%timeit bins, edges = bh.numpy.histogram(norm_vals, bins=100, range=(0, 10)) .. code-block:: text 7.3 ms ± 55.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) This is only a hair slower than using the raw boost-histogram API, and is still a nice performance boost over NumPy. You can even use the NumPy syntax if you want a boost-histogram object later: .. code-block:: python3 hist = bh.numpy.histogram(norm_vals, bins=100, range=(0, 10), histogram=bh.Histogram) You can later get a NumPy style output tuple from a histogram object: .. code-block:: python3 bins, edges = hist.to_numpy() So you can transition your code slowly to boost-histogram. 2D Histogram example ^^^^^^^^^^^^^^^^^^^^ .. code-block:: python3 data = np.random.multivariate_normal((0, 0), ((1, 0), (0, 0.5)), 10_000_000).T.copy() We can check the performance against NumPy again; NumPy does not do well with regular spaced bins in more than 1D: .. code-block:: ipython %%timeit np.histogram2d(*data, bins=(400, 200), range=((-2, 2), (-1, 1))) .. code-block:: text 1.31 s ± 17.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) .. code-block:: ipython %%timeit bh.numpy.histogram2d(*data, bins=(400, 200), range=((-2, 2), (-1, 1))) .. code-block:: text 101 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) For more than one dimension, boost-histogram is more than an order of magnitude faster than NumPy for regular spaced binning. Although optimizations may be added to boost-histogram for common axes combinations later, in 0.6.1, all axes combinations share a common code base, so you can expect *at least* this level of performance regardless of the axes types or number of axes! Threaded filling can give you an even larger performance boost if you have multiple cores and a large fill to perform. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/plotting.rst000066400000000000000000000007241516627434300247250ustar00rootroot00000000000000.. _usage-plotting: Plotting ======== Boost-histogram does not contain plotting functions - this is outside of the scope of the project, which is histogram filling and manipulation. However, it does follow ``PlottableProtocol``. Any plotting library that accepts an object that follows the ``PlottableProtocol`` can plot boost-histogram objects. Read about the ``PlottableProtocol`` in the `UHI plotting `_ page. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/quickstart.rst000066400000000000000000000112541516627434300252570ustar00rootroot00000000000000.. _usage-quickstart: Quickstart ========== All of the examples will assume the following import: .. code-block:: python3 import boost_histogram as bh In boost-histogram, a histogram is collection of Axis objects and a storage. .. image:: ../_images/histogram_design.png :alt: Regular axis illustration :align: center Making a histogram ------------------ You can make a histogram like this: .. code-block:: python3 hist = bh.Histogram(bh.axis.Regular(bins=10, start=0, stop=1)) If you’d like to type less, you can leave out the keywords: .. code-block:: python3 hist = bh.Histogram(bh.axis.Regular(10, 0, 1)) The exact same syntax is used for 1D, 2D, and ND histograms: .. code-block:: python3 hist3D = bh.Histogram( bh.axis.Regular(10, 0, 100, circular=True), bh.axis.Regular(10, 0.0, 10.0), bh.axis.Variable([1, 2, 3, 4, 5, 5.5, 6]), ) See :ref:`usage-axes` and :ref:`usage-transforms`. You can also select a different storage with the ``storage=`` keyword argument; see :ref:`usage-storage` for details about the other storages. Filling a histogram ------------------- Once you have a histogram, you can fill it using ``.fill``. Ideally, you should give arrays, but single values work as well: .. code-block:: python3 hist = bh.Histogram(bh.axis.Regular(10, 0.0, 1.0)) hist.fill(0.9) hist.fill([0.9, 0.3, 0.4]) Slicing and rebinning --------------------- You can slice into a histogram using bin coordinates or data coordinates using ``bh.loc(v)``. You can also rebin with ``bh.rebin(n)`` or remove an entire axis using ``sum`` (technically as the third slice argument, though it is allowed by itself as well): .. code-block:: python3 hist = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1), ) mini = hist[1:5, bh.loc(0.2) : bh.loc(0.9), sum] # Will be 4 bins x 7 bins See :ref:`usage-indexing`. Accessing the contents ---------------------- You can use ``hist.values()`` to get a NumPy array from any histogram. You can get the variances with ``hist.variances()``, though if you fill an unweighted storage with weights, this will return None, as you no longer can compute the variances correctly (please use a weighted storage if you need to). You can also get the number of entries in a bin with ``.counts()``; this will return counts even if your storage is a mean storage. See :ref:`usage-plotting`. If you want access to the full underlying storage, ``.view()`` will return a NumPy array for simple storages or a RecArray-like wrapper for non-simple storages. Most methods offer an optional keyword argument that you can pass, ``flow=True``, to enable the under and overflow bins (disabled by default). .. code-block:: python3 np_array = hist.view() Setting the contents -------------------- You can set the contents directly as you would a NumPy array; you can set either values or arrays at a time: .. code-block:: python3 hist[2] = 3.5 hist[bh.underflow] = 0 # set the underflow bin hist2d[3:5, 2:4] = np.eye(2) # set with array For non-simple storages, you can add an extra dimension that matches the constructor arguments of that accumulator. For example, if you want to fill a Weight histogram with three values, you can dimension: .. code-block:: python3 hist[0:3] = [[1, 0.1], [2, 0.2], [3, 0.3]] See :ref:`usage-indexing`. Accessing Axes -------------- The axes are directly available in the histogram, and you can access a variety of properties, such as the ``edges`` or the ``centers``. All properties and methods are also available directly on the ``axes`` tuple: .. code-block:: python3 ax0 = hist.axes[0] X, Y = hist.axes.centers See :ref:`usage-axes`. Saving Histograms ----------------- You can save histograms using pickle: .. code-block:: python3 import pickle with open("file.pkl", "wb") as f: pickle.dump(h, f) with open("file.pkl", "rb") as f: h2 = pickle.load(f) assert h == h2 Special care was taken to ensure that this is fast and efficient. Please use the latest version of the Pickle protocol you feel comfortable using; you cannot use version 0, the version that was default on Python 2. The most recent versions provide performance benefits. Computing with Histograms ------------------------- As an complete example, let's say you wanted to compute and plot the density: .. literalinclude:: ../../examples/simple_density.py :language: python .. image:: ../_images/ex_hist_density.png :alt: Density histogram output :align: center Comparing with Boost.Histogram ------------------------------ This is built on the Boost.Histogram library. See :ref:`usage-comparison`. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/storage.rst000066400000000000000000000116451516627434300245350ustar00rootroot00000000000000.. _usage-storage: Storages ======== There are several storages to choose from. To select a storage, pass the ``storage=bh.storage.`` argument when making a histogram. Simple storages --------------- These storages hold a single value that keeps track of a count, possibly a weighed count. Double ^^^^^^ By default, boost-histogram selects the ``Double()`` storage. For most uses, this should be ideal. It is just as fast as the ``Int64()`` storage, it can fill up to 53 bits of information (9 quadrillion) counts per cell, and supports weighted fills. It can also be scaled by a floating point values without making a copy. .. code-block:: python3 h = bh.Histogram(bh.axis.Regular(10, 0, 1)) # Double() is the default h.fill([0.2, 0.3], weight=[0.5, 2]) # Weights are optional print(f"{h[bh.loc(.2)]=}\n{h[bh.loc(.3)]=}") # Python 3.8 print .. code-block:: text h[bh.loc(.2)]=0.5 h[bh.loc(.3)]=2.0 Unlimited ^^^^^^^^^ The Unlimited storage starts as an 8-bit integer and grows, and converts to a double if weights are used (or, currently, if a view is requested). This allows you to keep the memory usage minimal, at the expense of occasionally making an internal copy. Int64 ^^^^^ A true integer storage is provided, as well; this storage has the ``np.uint64`` datatype. This eventually should provide type safety by not accepting non-integer fills for data that should represent raw, unweighed counts. .. code-block:: python3 h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=bh.storage.Int64()) h.fill([0.2, 0.3], weight=[1, 2]) # Integer weights supported print(f"{h[bh.loc(.2)]=}\n{h[bh.loc(.2)]=}") .. code-block:: text h[bh.loc(.2)]=1 h[bh.loc(.3)]=2 AtomicInt64 ^^^^^^^^^^^ This storage is like ``Int64()``, but also provides a thread safety guarantee. You can fill a single histogram from multiple threads. Accumulator storages -------------------- These storages hold more than one number internally. They return a smart view when queried with ``.view()``; see :ref:`usage-accumulators` for information on each accumulator and view. Weight ^^^^^^ This storage keeps a sum of weights as well (in CERN ROOT, this is like calling ``.Sumw2()`` before filling a histogram). It uses the ``WeightedSum`` accumulator. MultiCell ^^^^^^^^^ This storage is like the ``Double`` storage but supports storing multiple entries per bin independently. This is useful if one has to deal with many independent weights per event that all correspond to the same parameter (they will a be binned into the same bin) and have to be summed independently (e.g. to track the effect of systematic variations). It is supposed to be filled much faster compared to filling many histograms with a ``Weight`` storage type in a loop if one deals with a large number of different weights. The number of entries per bin has to be fixed and is provided to the storage at its construction through .. code-block:: python3 bh.Histogram(…, storage=MultiCell(nelem)) where ``nelem`` is the number of entries per bin. The entries have to provided as a 2-dimensional array ``(n, nelem)`` with the first dimension being the events to histogram and the second axis being the entries per event. To fill a histogram ``h`` one has to provide the entries via the ``weight`` keyword: .. code-block:: python3 h.fill(..., weight=weights) Any slicing or projection operation works for ``MultiCell`` histograms identical to any other histogram with different storage type, the entries are here not considered an additional axis for the histogram. Calling ``h.view()`` returns an array where the entries are indexed as the first axis (e.g. ``h.view()[0]`` is the histogram content for the first entry per bin). Contrary to the ``Weight`` storage the ``MultiCell`` storage does not track variances (it does not track the sum of weights squared) because this might not be necessary for every entry index. Instead, the user is supposed to track the variances themselves if required. This could be achieved by providing the variances as a separate entry to the ``MultiCell`` histogram by increasing the number of entries that are stored per bin (e.g. one could provide the square of ``weights[:, 0]`` as ``weights[:, 1]`` to track the sum of weight squared of the first entry index in the second entry index). Note: If you should ever need to use the lowlevel ``h._hist.fill()`` function with a ``MultiCell`` storage you will have to use the ``sample`` keyword to pass the weights instead of the ``weight`` keyword because that is used on the C++ side, but the highlevel python boost_histogram API hides this from the user. Mean ^^^^ This storage tracks a "Profile", that is, the mean value of the accumulation instead of the sum. It stores the count (as a double), the mean, and a term that is used to compute the variance. When filling, you must add a ``sample=`` term. WeightedMean ^^^^^^^^^^^^ This is similar to Mean, but also keeps track a sum of weights like term as well. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/subclassing.rst000066400000000000000000000053701516627434300254040ustar00rootroot00000000000000.. _usage-subclassing: Subclassing (advanced) ====================== Subclassing boost-histogram components is supported, but requires a little extra care to ensure the subclasses do not return un-wrapped boost-histogram components when a subclassed version is available. The issue is that various actions make the C++ -> Python transition over again, such as using ``.project()``. For example, let's say you have a ``MyHistogram`` and a ``MyRegular``. If you use ``project(0)``, that needs to also return a ``MyRegular``, but it is reconverting the return value from C++ to Python, so it has to somehow know that ``MyRegular`` is the right axis subclass to select from for ``MyHistogram``. This is accomplished with families. When you subclass, you will need to add a family. Any object can be used - the module for your library is a good choice if you only have one "family" of histograms. Boost-histogram uses ``boost_histogram``, Hist uses ``hist``. You can use anything you want, though; a custom tag object like ``MY_FAMILY = object()`` works well too. It just has to support ``is``, and be the exact same object on all your subclasses. .. code-block:: python3 import boost_histogram as bh import my_package class Histogram(bh.Histogram, family=my_package): ... class Regular(bh.axis.Regular, family=my_package): ... If you only override ``Histogram``, you can leave off the ``family=`` argument, or set it to ``None``. It will generate a private ``object()`` in this case. You must add an explicit family to ``Histogram`` if you subclass any further components. If you use Mixins, special care needs to be taken if you need a left-acting Mixin, since class keywords are handled via ``super()`` left to right. This is a Mixin that will work on either side: .. code-block:: python3 class AxisMixin: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) # type: ignore Mixins are recommended if you want to provide functionality to a collection of different subclasses, like ``Axis``. There are customization hooks provided for subclasses as well. ``self._generate_axes_()`` is called to produce an ``AxesTuple``, so you can override that if you customize ``AxesTuple``. ``_import_bh_`` and ``_export_bh_`` are called when converting an object between histogram libraries. ``cls._export_bh_(self)`` is called from the outgoing class (being converted from), and ``self._import_bh_()`` is called afterward on the incoming class (being converted to). So if ``h1`` is an instance of ``H1``, and ``H2`` is the new class, then ``H2(h1)`` calls ``H1._export_bh_(h2)`` and then ``h2._import_bh_()`` before returning ``h2``. The internal repr building for axes is a list produced by ``_repr_args_`` representing each item in the repr. scikit-hep-boost-histogram-5f6f3e3/docs/user-guide/transforms.rst000066400000000000000000000140641516627434300252650ustar00rootroot00000000000000.. _usage-transforms: Using Transforms ================ The boost-histogram library provides a powerful transform system on Regular axes that allows you to provide a functional form for the conversion between a regular spacing and the actual bin edges. The following transforms are built in: * ``bh.axis.transform.sqrt``: A square root transform * ``bh.axis.transform.log``: A logarithmic transform * ``bh.axis.transform.Pow(power)`` Raise to a specified power (``power=0.5`` is identical to ``sqrt``) There is also a flexible ``bh.axis.transform.Function``, which allows you to specify arbitrary conversion functions (detailed below). Simple custom transforms ------------------------ The ``Function`` transform takes two ctypes ``double(double)`` function pointers, a forward transform and a inverse transform. An object that provides a ctypes function pointer through a ``.ctypes`` attribute is supported, as well. As an example, let's look at how one would recreate the ``log`` transform using several different methods: Pure Python ^^^^^^^^^^^ You can directly cast a python callable to a ctypes pointer, and use that. However, you will call Python *every* time you interact with the transformed axis, and this will be 15-90 times slower than a compiled method, like ``bh.axis.transform.log``. In most cases, a Variable axis will be faster. .. code-block:: python import ctypes ftype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) # Pure Python (15x slower) bh.axis.Regular( 10, 1, 4, transform=bh.axis.transform.Function(ftype(math.log), ftype(math.exp)) ) # Pure Python: NumPy (90x slower) bh.axis.Regular( 10, 1, 4, transform=bh.axis.transform.Function(ftype(np.log), ftype(np.exp)) ) You can create a Variable axis from the edges of this axis; often that will be faster. You can also use ``transform=ftype`` and just directly provide the functions; this provides nicer reprs, but is still not picklable because ftype is a generated and not picklable; see below for a way to make this picklable. You can also specify ``name="..."`` to customize the repr explicitly. Using Numba ^^^^^^^^^^^ If you have the numba library installed, and your transform is reasonably simple, you can use the ``@numba.cfunc`` decorator to create a callable that will run directly through the C interface. This is just as fast as the compiled version provided! .. code-block:: python import numba @numba.cfunc(numba.float64(numba.float64)) def exp(x): return math.exp(x) @numba.cfunc(numba.float64(numba.float64)) def log(x): return math.log(x) bh.axis.Regular(10, 1, 4, transform=bh.axis.transform.Function(log, exp)) Manual compilation ^^^^^^^^^^^^^^^^^^ You can also get a ctypes pointer from the usual place: a library. Let's say you have the following ``mylib.c`` file: .. code-block:: c #include double my_log(double value) { return log(value); } double my_exp(double value) { return exp(value); } And you compile it with: .. code-block:: bash gcc mylib.c -shared -o mylib.so You can now use it like this: .. code-block:: python import ctypes ftype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) mylib = ctypes.CDLL("mylib.so") my_log = ctypes.cast(mylib.my_log, ftype) my_exp = ctypes.cast(mylib.my_exp, ftype) bh.axis.Regular(10, 1, 4, transform=bh.axis.transform.Function(my_log, my_exp)) Note that you do actually have to cast it to the correct function type; just setting ``argtypes`` and ``restype`` does not work. Picklable custom transforms --------------------------- The above examples to not support pickling, since ctypes pointers (or pointers in general) are not picklable. However, the ``Function`` transform supports a ``convert=`` keyword argument that takes the two provided objects and converts them to ctypes pointers. So if you can supply a pair of picklable objects and a conversion function, you can make a fully picklable transform. A few common cases are given below. Pure Python ^^^^^^^^^^^ This is the easiest example; as long as your Python function is picklable, all you need to do is move the ctypes call into the convert function. You need a little wrapper function to make it picklable: .. code-block:: python import ctypes, math # We need a little wrapper function only because ftype is not directly picklable def convert_python(func): ftype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) return ftype(func) bh.axis.Regular( 10, 1, 4, transform=bh.axis.transform.Function(math.log, math.exp, convert=convert_python), ) That's it. Using Numba ^^^^^^^^^^^ The same procedure works for numba decorators. NumPy only supports functions, not builtins like ``math.log``, so if you want to pass those, you'll need to wrap them in a lambda function or add a bit of logic to the convert function. Here are your options: .. code-block:: python import numba, math def convert_numba(func): return numba.cfunc(numba.double(numba.double))(func) # Built-ins and ufuncs need to be wrapped (numba can't read a signature) # User functions would not need the lambda bh.axis.Regular( 10, 1, 4, transform=bh.axis.transform.Function( lambda x: math.log(x), lambda x: math.exp(x), convert=convert_numba ), ) Note that ``numba.cfunc`` does not work on its own builtins, but requires a user function. Since with the exception of the simple example I'm showing here that is already available directly in boost-histogram, you will probably be composing your own functions out of more than one builtin operation, you generally will not need the lambda here. Manual compilation ^^^^^^^^^^^^^^^^^^ You can use strings to look up functions in the shared library: .. code-block:: python def lookup(name): mylib = ctypes.CDLL("mylib.so") function = getattr(mylib, name) return ctypes.cast(function, ftype) bh.axis.Regular( 10, 1, 4, transform=bh.axis.transform.Function("my_log", "my_exp", convert=lookup) ) scikit-hep-boost-histogram-5f6f3e3/examples/000077500000000000000000000000001516627434300211455ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/examples/README.md000066400000000000000000000005421516627434300224250ustar00rootroot00000000000000# Boost Histogram examples The examples require Python 3.10. It is left as an exercise for the reader to convert back to older versions if they so desire. ### Setup You can run these examples in a virtual environment using: #### Conda ```bash conda create -p .env python=3.9 boost-histogram matplotlib numpy -c conda-forge conda activate ./.env ``` scikit-hep-boost-histogram-5f6f3e3/examples/cpp/000077500000000000000000000000001516627434300217275ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/examples/cpp/CMakeLists.txt000066400000000000000000000017741516627434300245000ustar00rootroot00000000000000# This file lets you quickly build projects with the exact same boost-histogram # settings that the Python bindings use. cmake_minimum_required(VERSION 3.24...3.31) project(BOOST_HISTOGRAM_CPP LANGUAGES CXX) add_library(boost_histogram_cpp IMPORTED INTERFACE) # these are the boost header-only libraries required by boost::histogram target_include_directories( boost_histogram_cpp INTERFACE ../../extern/assert/include ../../extern/config/include ../../extern/core/include ../../extern/histogram/include ../../extern/mp11/include ../../extern/throw_exception/include ../../extern/variant2/include) target_compile_options( boost_histogram_cpp INTERFACE $,/fp:fast,-funsafe-math-optimizations>) target_compile_features(boost_histogram_cpp INTERFACE cxx_std_14) if(PROJECT_NAME STREQUAL CMAKE_PROJECT_NAME) add_executable(simple simple.cpp) target_link_libraries(simple PRIVATE boost_histogram_cpp) endif() scikit-hep-boost-histogram-5f6f3e3/examples/cpp/simple.cpp000066400000000000000000000003331516627434300237230ustar00rootroot00000000000000#include #include int main() { namespace bh = boost::histogram; auto h = bh::make_histogram(bh::axis::category{}); std::cout << bh::algorithm::sum(h) << std::endl; } scikit-hep-boost-histogram-5f6f3e3/examples/simple_2d.py000077500000000000000000000015601516627434300234020ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import matplotlib.pyplot as plt import numpy as np import boost_histogram as bh # Create 2d-histogram with two axes with 20 equidistant bins from -3 to 3 h = bh.Histogram( bh.axis.Regular(20, -3, 3, metadata="x"), bh.axis.Regular(20, -3, 3, metadata="y") ) # Generate some NumPy arrays with data to fill into histogram, # in this case normal distributed random numbers in x and y x_data = np.random.randn(1_000) y_data = 0.5 * np.random.randn(1_000) # Fill histogram with NumPy arrays, this is very fast h.fill(x_data, y_data) # Get numpy.histogram compatible representation of the histogram w, x, y = h.to_numpy() # Draw the count matrix fig, ax = plt.subplots() ax.pcolormesh(x, y, w.T) ax.set_xlabel(h.axes[0].metadata) ax.set_ylabel(h.axes[1].metadata) ax.set_aspect("equal") plt.savefig("simple_2d.png") scikit-hep-boost-histogram-5f6f3e3/examples/simple_density.py000077500000000000000000000012631516627434300245540ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import functools import operator import matplotlib.pyplot as plt import numpy as np import boost_histogram as bh # Make a 2D histogram hist = bh.Histogram(bh.axis.Regular(50, -3, 3), bh.axis.Regular(50, -3, 3)) # Fill with Gaussian random values hist.fill(np.random.normal(size=1_000_000), np.random.normal(size=1_000_000)) # Compute the areas of each bin areas = functools.reduce(operator.mul, hist.axes.widths) # Compute the density density = hist.view() / hist.sum() / areas # Make the plot fig, ax = plt.subplots() mesh = ax.pcolormesh(*hist.axes.edges.T, density.T) fig.colorbar(mesh) plt.savefig("simple_density.png") scikit-hep-boost-histogram-5f6f3e3/examples/simple_log_weight.py000077500000000000000000000022461516627434300252270ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import boost_histogram as bh # make 1-d histogram with 5 logarithmic bins from 1e0 to 1e5 h = bh.Histogram( bh.axis.Regular(5, 1e0, 1e5, metadata="x", transform=bh.axis.transform.log), storage=bh.storage.Weight(), ) # fill histogram with numbers x = (2e0, 2e1, 2e2, 2e3, 2e4) h.fill(x, weight=4) # increment bin counter by 4 # iterate over bins and access bin counter for idx, (lower, upper) in enumerate(h.axes[0]): val = h[idx] print(f"bin {idx} [{lower:g}, {upper:g}): {val.value} +/- {val.variance**0.5}") # under- and overflow bin lo, up = h.axes[0][bh.underflow] print( f"underflow [{lo:g}, {up:g}): {h[bh.underflow].value} +/- {h[bh.underflow].variance ** 0.5}" ) lo, up = h.axes[0][bh.overflow] print( f"overflow [{lo:g}, {up:g}): {h[bh.overflow].value} +/- {h[bh.overflow].variance ** 0.5}" ) # prints: # bin 0 x in [1.0, 10.0): 4.0 +/- 4.0 # bin 1 x in [10.0, 100.0): 4.0 +/- 4.0 # bin 2 x in [100.0, 1000.0): 4.0 +/- 4.0 # bin 3 x in [1000.0, 10000.0): 4.0 +/- 4.0 # bin 4 x in [10000.0, 100000.0): 4.0 +/- 4.0 # underflow [0.0, 1.0): 0.0 +/- 0.0 # overflow [100000.0, inf): 0.0 +/- 0.0 scikit-hep-boost-histogram-5f6f3e3/examples/simple_numpy.py000077500000000000000000000016241516627434300242460ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import matplotlib.pyplot as plt import numpy as np import boost_histogram as bh # Create 2d-histogram with two axes with 10 equidistant bins from -3 to 3 h = bh.Histogram( bh.axis.Regular(10, -3, 3, metadata="x"), bh.axis.Regular(10, -3, 3, metadata="y") ) # Generate some NumPy arrays with data to fill into histogram, # in this case normal distributed random numbers in x and y x_data = np.random.randn(1000) y_data = 0.5 * np.random.randn(1000) # Fill histogram with numpy arrays, this is very fast h.fill(x_data, y_data) # Get representations of the bin edges as NumPy arrays x = h.axes[0].edges y = h.axes[1].edges # Creates a view of the counts (no copy involved) count_matrix = h.view() # Draw the count matrix plt.pcolor(x, y, count_matrix.T) plt.xlabel(h.axes[0].metadata) plt.ylabel(h.axes[1].metadata) plt.savefig("simple_numpy.png") scikit-hep-boost-histogram-5f6f3e3/examples/simple_pickly.py000077500000000000000000000011561516627434300243710ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import pickle from pathlib import Path import boost_histogram as bh h1 = bh.Histogram(bh.axis.Regular(2, -1, 1)) h2 = h1.copy() h1.fill(-0.5) h2.fill(0.5) # Arithmetic operators h3 = h1 + h2 h4 = h3 * 2 print(f"h4[0] = {h4[0]}, h4[1] = {h4[1]}") h4_saved = Path("h4_saved.pkl") # Now save the histogram with h4_saved.open("wb") as f: pickle.dump(h4, f, protocol=-1) # And load with h4_saved.open("rb") as f: h5 = pickle.load(f) assert h4 == h5 print("Succeeded in pickling a histogram!") # Delete the file to keep things tidy h4_saved.unlink() scikit-hep-boost-histogram-5f6f3e3/examples/simple_weight.py000077500000000000000000000012571516627434300243670ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import boost_histogram as bh # Make 1-d histogram with 5 logarithmic bins from 1e0 to 1e5 h = bh.Histogram( bh.axis.Regular(5, 1e0, 1e5, metadata="x", transform=bh.axis.transform.log), storage=bh.storage.Weight(), ) # Fill histogram with numbers x = (2e0, 2e1, 2e2, 2e3, 2e4) # Doing this several times so the variance is more interesting h.fill(x, weight=2) h.fill(x, weight=2) h.fill(x, weight=2) h.fill(x, weight=2) # Iterate over bins and access bin counter for idx, (lower, upper) in enumerate(h.axes[0]): val = h[idx] print(f"bin {idx} in [{lower:g}, {upper:g}): {val.value} +/- {val.variance**0.5}") scikit-hep-boost-histogram-5f6f3e3/extern/000077500000000000000000000000001516627434300206345ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/assert/000077500000000000000000000000001516627434300221355ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/config/000077500000000000000000000000001516627434300221015ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/core/000077500000000000000000000000001516627434300215645ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/histogram/000077500000000000000000000000001516627434300226315ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/mp11/000077500000000000000000000000001516627434300214125ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/throw_exception/000077500000000000000000000000001516627434300240555ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/extern/variant2/000077500000000000000000000000001516627434300223625ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/include/000077500000000000000000000000001516627434300207525ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/include/bh_python/000077500000000000000000000000001516627434300227445ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/include/bh_python/accumulators/000077500000000000000000000000001516627434300254465ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/include/bh_python/accumulators/mean.hpp000066400000000000000000000062671516627434300271120ustar00rootroot00000000000000// Copyright 2015-2019 Hans Dembinski and Henry Schreiner // // Distributed under the Boost Software License, version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/mean.hpp // Changes: // * Internal values are public for access from Python // * A special constructor added for construction from Python #pragma once #include #include namespace accumulators { /** Calculates mean and variance of sample. Uses Welford's incremental algorithm to improve the numerical stability of mean and variance computation. */ template struct mean { using value_type = ValueType; using const_reference = const value_type&; mean() = default; mean(const value_type& n, const value_type& mean, const value_type& variance) noexcept : count(n) , value(mean) , _sum_of_deltas_squared(variance * (n - 1)) {} mean(const value_type& sum, const value_type& mean, const value_type& _sum_of_deltas_squared, bool /* Tag to trigger python internal constructor */) : count(sum) , value(mean) , _sum_of_deltas_squared(_sum_of_deltas_squared) {} void operator()(const value_type& x) noexcept { count += static_cast(1); const auto delta = x - value; value += delta / count; _sum_of_deltas_squared += delta * (x - value); } void operator()(const boost::histogram::weight_type& w, const value_type& x) noexcept { count += w.value; const auto delta = x - value; value += w.value * delta / count; _sum_of_deltas_squared += w.value * delta * (x - value); } mean& operator+=(const mean& rhs) noexcept { if(rhs.count == 0) return *this; const auto mu1 = value; const auto mu2 = rhs.value; const auto n1 = count; const auto n2 = rhs.count; count += rhs.count; value = (n1 * mu1 + n2 * mu2) / count; _sum_of_deltas_squared += rhs._sum_of_deltas_squared; _sum_of_deltas_squared += n1 * (value - mu1) * (value - mu1) + n2 * (value - mu2) * (value - mu2); return *this; } mean& operator*=(const value_type& s) noexcept { value *= s; _sum_of_deltas_squared *= s * s; return *this; } bool operator==(const mean& rhs) const noexcept { return count == rhs.count && value == rhs.value && _sum_of_deltas_squared == rhs._sum_of_deltas_squared; } bool operator!=(const mean& rhs) const noexcept { return !operator==(rhs); } value_type variance() const noexcept { return _sum_of_deltas_squared / (count - 1); } template void serialize(Archive& ar, unsigned) { ar& boost::make_nvp("count", count); ar& boost::make_nvp("value", value); ar& boost::make_nvp("_sum_of_deltas_squared", _sum_of_deltas_squared); } value_type count{}; value_type value{}; value_type _sum_of_deltas_squared{}; }; } // namespace accumulators scikit-hep-boost-histogram-5f6f3e3/include/bh_python/accumulators/ostream.hpp000066400000000000000000000056601516627434300276400ustar00rootroot00000000000000// Copyright 2015-2020 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. // // Based on boost/histogram/axis/ostream.hpp // String representations here evaluate correctly in Python. #pragma once #include #include #include #include #include #include #include /** \file boost/histogram/accumulators/ostream.hpp Simple streaming operators for the builtin accumulator types. Mostly similar to boost/histogram/accumulators/ostream.hpp */ template std::basic_ostream& handle_nonzero_width(std::basic_ostream& os, const T& x) { const auto w = os.width(); os.width(0); std::streamsize count = 0; { auto g = ::boost::histogram::detail::make_count_guard(os, count); os << x; } if(os.flags() & std::ios::left) { os << x; for(auto i = count; i < w; ++i) os << os.fill(); } else { for(auto i = count; i < w; ++i) os << os.fill(); os << x; } return os; } // Note that the names are *not* included here, so they can be added in pybind11. namespace accumulators { template std::basic_ostream& operator<<(std::basic_ostream& os, const weighted_sum& x) { if(os.width() == 0) return os << "value=" << x.value << ", variance=" << x.variance; return handle_nonzero_width(os, x); } template std::basic_ostream& operator<<(std::basic_ostream& os, const mean& x) { if(os.width() == 0) return os << "count=" << x.count << ", value=" << x.value << ", variance=" << x.variance(); return handle_nonzero_width(os, x); } template std::basic_ostream& operator<<(std::basic_ostream& os, const weighted_mean& x) { if(os.width() == 0) return os << "sum_of_weights=" << x.sum_of_weights << ", sum_of_weights_squared=" << x.sum_of_weights_squared << ", value=" << x.value << ", variance=" << x.variance(); return handle_nonzero_width(os, x); } template std::basic_ostream& operator<<(std::basic_ostream& os, const ::boost::histogram::accumulators::count& x) { os << x.load(); return os; } } // namespace accumulators scikit-hep-boost-histogram-5f6f3e3/include/bh_python/accumulators/weighted_mean.hpp000066400000000000000000000101161516627434300307560ustar00rootroot00000000000000// Copyright 2018-2019 Hans Dembinski and Henry Schreiner // // Distributed under the Boost Software License, version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/weighted_mean.hpp // // Changes: // * Internal values are public for access from Python // * A special constructor added for construction from Python #pragma once #include #include namespace accumulators { /** Calculates mean and variance of weighted sample. Uses West's incremental algorithm to improve numerical stability of mean and variance computation. */ template struct weighted_mean { using value_type = ValueType; using const_reference = const value_type&; weighted_mean() = default; weighted_mean(const value_type& wsum, const value_type& wsum2, const value_type& mean, const value_type& variance) : sum_of_weights(wsum) , sum_of_weights_squared(wsum2) , value(mean) , _sum_of_weighted_deltas_squared( variance * (sum_of_weights - sum_of_weights_squared / sum_of_weights)) {} weighted_mean(const value_type& wsum, const value_type& wsum2, const value_type& mean, const value_type& _sum_of_weighted_deltas_squared, bool /* tag to trigger Python internal constructor */) : sum_of_weights(wsum) , sum_of_weights_squared(wsum2) , value(mean) , _sum_of_weighted_deltas_squared(_sum_of_weighted_deltas_squared) {} void operator()(const value_type& x) { operator()(boost::histogram::weight(1), x); } void operator()(const boost::histogram::weight_type& w, const value_type& x) { sum_of_weights += w.value; sum_of_weights_squared += w.value * w.value; const auto delta = x - value; value += w.value * delta / sum_of_weights; _sum_of_weighted_deltas_squared += w.value * delta * (x - value); } weighted_mean& operator+=(const weighted_mean& rhs) { if(rhs.sum_of_weights == 0) return *this; const auto mu1 = value; const auto mu2 = rhs.value; const auto n1 = sum_of_weights; const auto n2 = rhs.sum_of_weights; sum_of_weights += rhs.sum_of_weights; sum_of_weights_squared += rhs.sum_of_weights_squared; value = (n1 * mu1 + n2 * mu2) / sum_of_weights; _sum_of_weighted_deltas_squared += rhs._sum_of_weighted_deltas_squared; _sum_of_weighted_deltas_squared += n1 * (value - mu1) * (value - mu1) + n2 * (value - mu2) * (value - mu2); return *this; } weighted_mean& operator*=(const value_type& s) { value *= s; _sum_of_weighted_deltas_squared *= s * s; return *this; } bool operator==(const weighted_mean& rhs) const noexcept { return sum_of_weights == rhs.sum_of_weights && sum_of_weights_squared == rhs.sum_of_weights_squared && value == rhs.value && _sum_of_weighted_deltas_squared == rhs._sum_of_weighted_deltas_squared; } bool operator!=(const weighted_mean rhs) const noexcept { return !operator==(rhs); } value_type variance() const { return _sum_of_weighted_deltas_squared / (sum_of_weights - sum_of_weights_squared / sum_of_weights); } template void serialize(Archive& ar, unsigned /* version */) { ar& boost::make_nvp("sum_of_weights", sum_of_weights); ar& boost::make_nvp("sum_of_weights_squared", sum_of_weights_squared); ar& boost::make_nvp("value", value); ar& boost::make_nvp("_sum_of_weighted_deltas_squared", _sum_of_weighted_deltas_squared); } value_type sum_of_weights{}; value_type sum_of_weights_squared{}; value_type value{}; value_type _sum_of_weighted_deltas_squared{}; }; } // namespace accumulators scikit-hep-boost-histogram-5f6f3e3/include/bh_python/accumulators/weighted_sum.hpp000066400000000000000000000052231516627434300306450ustar00rootroot00000000000000// Copyright 2015-2019 Hans Dembinski and Henry Schreiner // // Distributed under the Boost Software License, version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/weighted_mean.hpp // // Changes: // * Internal values are public for access from Python // * A special constructor added for construction from Python #pragma once #include #include namespace accumulators { /// Holds sum of weights and its variance estimate template struct weighted_sum { using value_type = ValueType; using const_reference = const value_type&; weighted_sum() = default; /// Initialize sum to value and allow implicit conversion explicit weighted_sum(const_reference value) noexcept : weighted_sum(value, value) {} /// Allow implicit conversion from sum template explicit weighted_sum(const weighted_sum& s) noexcept : weighted_sum(s.value(), s.variance()) {} /// Initialize sum to value and variance weighted_sum(const_reference value, const_reference variance) noexcept : value(value) , variance(variance) {} /// Increment by one. weighted_sum& operator++() { value += 1; variance += 1; return *this; } /// Increment by weight. template weighted_sum& operator+=(const bh::weight_type& w) { value += w.value; variance += w.value * w.value; return *this; } /// Added another weighted sum. weighted_sum& operator+=(const weighted_sum& rhs) { value += rhs.value; variance += rhs.variance; return *this; } /// Scale by value. weighted_sum& operator*=(const value_type& x) { value *= x; variance *= x * x; return *this; } bool operator==(const value_type& rhs) const noexcept { return value == rhs && variance == rhs; } template bool operator==(const weighted_sum& rhs) const noexcept { return value == rhs.value && variance == rhs.variance; } template bool operator!=(const T& rhs) const noexcept { return !operator==(rhs); } // lossy conversion must be explicit explicit operator const_reference() const { return value; } template void serialize(Archive& ar, unsigned /* version */) { ar& boost::make_nvp("value", value); ar& boost::make_nvp("variance", variance); } value_type value{}; value_type variance{}; }; } // namespace accumulators scikit-hep-boost-histogram-5f6f3e3/include/bh_python/array_like.hpp000066400000000000000000000024251516627434300256020ustar00rootroot00000000000000// Copyright 2018-2019 Hans Dembinski and Henry Schreiner // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include /// Generate empty array with same shape and strides as argument template py::array_t array_like(const py::object& obj) { if(!py::isinstance(obj)) { py::ssize_t shape[1] = {0}; // if scalar if(py::isinstance(obj) && !py::isinstance(obj)) { // if sequence auto seq = py::cast(obj); shape[0] = static_cast(seq.size()); } return py::array_t(shape); } auto arr = py::cast(obj); std::vector strides; strides.reserve(static_cast(arr.ndim())); for(int i = 0; i < arr.ndim(); ++i) { strides.emplace_back(arr.strides()[i] / arr.itemsize() * static_cast(sizeof(T))); } return py::array_t{boost::span{ arr.shape(), static_cast(arr.ndim())}, strides}; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/axis.hpp000066400000000000000000000314541516627434300244300ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace axis { namespace option = bh::axis::option; using ogrowth_t = decltype(option::growth | option::overflow); using uogrowth_t = decltype(option::growth | option::underflow | option::overflow); using circular_t = decltype(option::circular | option::overflow); // Must be specialized for each type (compile warning if not) template inline const char* string_name(); // Macro to make the string specializations more readable #define BHP_SPECIALIZE_NAME(name) \ template <> \ inline const char* string_name() { \ return #name; \ } // These match the Python names using regular_none = bh::axis::regular; using regular_uflow = bh::axis::regular; using regular_oflow = bh::axis::regular; using regular_uoflow = bh::axis::regular; using regular_uoflow_growth = bh::axis::regular; using regular_circular = bh::axis::regular; BHP_SPECIALIZE_NAME(regular_none) BHP_SPECIALIZE_NAME(regular_uflow) BHP_SPECIALIZE_NAME(regular_oflow) BHP_SPECIALIZE_NAME(regular_uoflow) BHP_SPECIALIZE_NAME(regular_uoflow_growth) BHP_SPECIALIZE_NAME(regular_circular) using regular_pow = bh::axis::regular; using regular_trans = bh::axis::regular; BHP_SPECIALIZE_NAME(regular_pow) BHP_SPECIALIZE_NAME(regular_trans) using variable_none = bh::axis::variable; using variable_uflow = bh::axis::variable; using variable_oflow = bh::axis::variable; using variable_uoflow = bh::axis::variable; using variable_uoflow_growth = bh::axis::variable; using variable_circular = bh::axis::variable; BHP_SPECIALIZE_NAME(variable_none) BHP_SPECIALIZE_NAME(variable_uflow) BHP_SPECIALIZE_NAME(variable_oflow) BHP_SPECIALIZE_NAME(variable_uoflow) BHP_SPECIALIZE_NAME(variable_uoflow_growth) BHP_SPECIALIZE_NAME(variable_circular) using integer_none = bh::axis::integer; using integer_uoflow = bh::axis::integer; using integer_uflow = bh::axis::integer; using integer_oflow = bh::axis::integer; using integer_growth = bh::axis::integer; using integer_circular = bh::axis::integer; BHP_SPECIALIZE_NAME(integer_none) BHP_SPECIALIZE_NAME(integer_uoflow) BHP_SPECIALIZE_NAME(integer_uflow) BHP_SPECIALIZE_NAME(integer_oflow) BHP_SPECIALIZE_NAME(integer_growth) BHP_SPECIALIZE_NAME(integer_circular) using category_int_none = bh::axis::category; using category_int = bh::axis::category; using category_int_growth = bh::axis::category; BHP_SPECIALIZE_NAME(category_int_none) BHP_SPECIALIZE_NAME(category_int) BHP_SPECIALIZE_NAME(category_int_growth) using category_str_none = bh::axis::category; using category_str = bh::axis::category; using category_str_growth = bh::axis::category; BHP_SPECIALIZE_NAME(category_str_none) BHP_SPECIALIZE_NAME(category_str) BHP_SPECIALIZE_NAME(category_str_growth) class boolean : public bh::axis::integer { public: explicit boolean(metadata_t meta = {}) : integer(0, 2, std::move(meta)) {} explicit boolean(const boolean& src, bh::axis::index_type begin, bh::axis::index_type end, unsigned merge) : integer(src, begin, end, merge) {} boolean(const boolean& other) = default; bh::axis::index_type index(int x) const noexcept { return integer::index(x == 0 ? 0 : 1); } // We can't specify inclusive, since this could be sliced }; // Built-in boolean requires bool fill, slower compile, not reducible // using boolean = bh::axis::boolean; BHP_SPECIALIZE_NAME(boolean) // Axis defined elsewhere BHP_SPECIALIZE_NAME(regular_numpy) #undef BHP_SPECIALIZE_NAME // How edges, centers, and widths are handled // // We distinguish between continuous and discrete axes. The integer axis is a borderline // case. It has discrete values, but they are consecutive. It is possible although not // correct to treat it like a continuous axis with bin width of 1. For the sake of // computing bin edges and bin center, we will use this ansatz here. PS: This behavior // is already implemented in Boost::Histogram when you create an integer axis with a // floating point value type, e.g. integer. In this case, the integer axis acts // strictly like a regular axis with a fixed bin with of 1. We don't use it here, // because it is slightly slower than the integer axis when the input values are // truly integers. // // The category axis is treated like regular(size(), 0, size()) in the conversion. It is // the responsibility of the user to set the labels accordingly when a histogram with a // category axis is plotted. template constexpr bool is_category(const A&) { return false; } template constexpr bool is_category(const bh::axis::category&) { return true; } template decltype(auto) select(Continuous&& c, Discrete&& d, Integer&&, const A& ax) { return bh::detail::static_if>( std::forward(c), std::forward(d), ax); } template decltype(auto) select(Continuous&&, Discrete&&, Integer&& i, const bh::axis::integer& ax) { return std::forward(i)(ax); } template decltype(auto) select(Continuous&&, Discrete&&, Boolean&& i, const boolean& ax) { return std::forward(i)(ax); } /// Return bin center for continuous axis and bin value for discrete axis template double unchecked_center(const A& ax, bh::axis::index_type i) { return select([i](const auto& ax) { return ax.value(i + 0.5); }, [i](const auto&) { return i + 0.5; }, [i](const auto& ax) { return ax.value(i) + 0.5; }, ax); } /// Return bin in a native Python representation template decltype(auto) unchecked_bin(const A& ax, bh::axis::index_type i) { return bh::detail::static_if>( [i](const auto& ax) -> decltype(auto) { return py::make_tuple(ax.value(i), ax.value(i + 1)); }, [i](const auto& ax) -> decltype(auto) { return (!is_category(ax) || i < ax.size()) ? py::cast(ax.bin(i)) : py::none(); }, ax); } /// Convert continuous axis into numpy.histogram compatible edge array template py::array_t edges(const A& ax, bool flow = false, bool numpy_upper = false) { auto continuous = [flow, numpy_upper](const auto& ax) { using AX = std::decay_t; using index_type = bh::axis::index_type; const index_type underflow = flow && (bh::axis::traits::get_options::test(option::underflow)); const index_type overflow = flow && (bh::axis::traits::get_options::test(option::overflow)); py::array_t edges( static_cast(ax.size() + 1 + overflow + underflow)); for(index_type i = -underflow; i <= ax.size() + overflow; ++i) edges.mutable_at(i + underflow) = ax.value(i); if(numpy_upper && !(std::is_same::value || std::is_same::value || std::is_same::value)) { edges.mutable_at(ax.size() + underflow) = std::nextafter( edges.at(ax.size() + underflow), std::numeric_limits::min()); } return edges; }; return select( continuous, [flow](const auto& ax) { using AX = std::decay_t; static_assert(!bh::axis::traits::get_options::test(option::underflow), "discrete axis never has underflow"); const bh::axis::index_type overflow = flow && bh::axis::traits::get_options::test(option::overflow); py::array_t edges( static_cast(ax.size() + 1 + overflow)); for(bh::axis::index_type i = 0; i <= ax.size() + overflow; ++i) edges.mutable_at(i) = i; return edges; }, continuous, ax); } template py::array_t centers(const A& ax) { py::array_t result(ax.size()); for(bh::axis::index_type i = 0; i < ax.size(); ++i) result.mutable_data()[i] = unchecked_center(ax, i); return result; } template py::array_t widths(const A& ax) { py::array_t result(ax.size()); bh::detail::static_if>( [](py::array_t& result, const auto& ax) { std::transform(ax.begin(), ax.end(), result.mutable_data(), [](const auto& b) { return b.width(); }); }, [](py::array_t& result, const auto& ax) { std::fill(result.mutable_data(), result.mutable_data() + ax.size(), 1.0); }, result, ax); return result; } } // namespace axis // The following list is all types supported using axis_variant = bh::axis::variant; // This saves a little typing using vector_axis_variant = std::vector; namespace pybind11 { namespace detail { /// Register axis_variant as a variant for pybind11 template <> struct type_caster : variant_caster {}; } // namespace detail } // namespace pybind11 scikit-hep-boost-histogram-5f6f3e3/include/bh_python/fill.hpp000066400000000000000000000212141516627434300244030ustar00rootroot00000000000000// Copyright 2018-2019 Hans Dembinski and Henry Schreiner // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace detail { namespace mp11 = boost::mp11; namespace variant = boost::variant2; static_assert( mp11::mp_empty>, mp11::mp_list>>::value, "supported value types are double, int, std::string; " "new axis was added with different value type"); template struct c_array_t : py::array_t { using base_t = py::array_t; using base_t::base_t; using base_t::operator=; std::size_t size() const { return static_cast(base_t::size()); } }; // not actually a numpy array template <> struct c_array_t : std::vector { using base_t = std::vector; using base_t::base_t; using base_t::operator=; // NOLINTNEXTLINE(google-explicit-constructor) c_array_t(base_t&& x) : base_t(std::move(x)) {} c_array_t& operator=(base_t&& x) { base_t::operator=(std::move(x)); return *this; } }; // for int, double template bool is_value(py::handle h) { if(py::isinstance(h) && py::cast(h).ndim() > 0) return false; return PyNumber_Check(h.ptr()); } // for std::string template <> inline bool is_value(py::handle h) { return py::isinstance(h) || (py::isinstance(h) && py::cast(h).ndim() == 0); } template decltype(auto) special_cast(py::handle x) { return py::cast(x); } // allow conversion of dim 0 arrays template <> inline decltype(auto) special_cast(py::handle x) { if(py::isinstance(x)) return py::cast(py::cast(x)); return py::cast(x); } // easier than specializing type_caster for c_array_t template <> inline decltype(auto) special_cast>(py::handle x) { using B = typename c_array_t::base_t; return py::cast(x); } // Make sure float arrays don't get cast to integers (-.5 rounds to 0!) template <> inline decltype(auto) special_cast>(py::handle x) { auto np = py::module::import("numpy"); auto dtype = py::cast(x).dtype(); if(dtype.equal(np.attr("bool_")) || dtype.equal(np.attr("int8")) || dtype.equal(np.attr("int16")) || dtype.equal(np.attr("int32")) || dtype.equal(np.attr("int64")) || dtype.equal(np.attr("uint8")) || dtype.equal(np.attr("uint16")) || dtype.equal(np.attr("uint32")) || dtype.equal(np.attr("uint64"))) return py::cast>(x); throw py::type_error("Only integer arrays supported when targeting integer axes"); } // Produce a type error for passing float to int template <> inline decltype(auto) special_cast(py::handle x) { try { return py::cast(x); } catch(std::runtime_error&) { throw py::type_error( "Only integer values supported when targeting integer axes"); } } using arg_t = variant::variant, double, c_array_t, int, c_array_t, std::string>; using weight_t = variant::variant>; inline auto get_vargs(const vector_axis_variant& axes, const py::args& args) { if(args.size() != axes.size()) throw std::invalid_argument("Wrong number of args"); auto vargs = bh::detail::make_stack_buffer(axes); bh::detail::for_each_axis( axes, [args_it = args.begin(), vargs_it = vargs.begin()](const auto& ax) mutable { using T = bh::axis::traits::value_type>; // T is one of: int, double, std::string const auto& x = *args_it++; auto& v = *vargs_it++; if(is_value(x)) { v = special_cast(x); } else { if(py::isinstance(x) && py::cast(x).ndim() != 1) throw std::invalid_argument("All arrays must be 1D"); v = special_cast>(x); } }); return vargs; } inline auto get_weight(py::kwargs& kwargs) { // default constructed as monostate to indicate absence of weight variant::variant> weight; auto w = optional_arg(kwargs, "weight"); if(!w.is_none()) { if(is_value(w)) weight = py::cast(w); else weight = py::cast>(w); } return weight; } // for accumulators that accept a weight template void fill_impl(bh::detail::accumulator_traits_holder, Histogram& h, const VArgs& vargs, const weight_t& weight, py::kwargs& kwargs) { none_only_arg(kwargs, "sample"); finalize_args(kwargs); // releasing gil here is safe, we don't manipulate refcounts const py::gil_scoped_release lock; variant::visit( overload([&h, &vargs](const variant::monostate&) { h.fill(vargs); }, [&h, &vargs](const auto& w) { h.fill(vargs, bh::weight(w)); }), weight); } // for accumulators that accept a weight and a double template void fill_impl(bh::detail::accumulator_traits_holder, Histogram& h, const VArgs& vargs, const weight_t& weight, py::kwargs& kwargs) { auto s = required_arg(kwargs, "sample"); finalize_args(kwargs); auto sarray = py::cast>(s); if(sarray.ndim() != 1) throw std::invalid_argument("Sample array must be 1D"); // releasing gil here is safe, we don't manipulate refcounts const py::gil_scoped_release lock; variant::visit( overload([&h, &vargs, &sarray]( const variant::monostate&) { h.fill(vargs, bh::sample(sarray)); }, [&h, &vargs, &sarray](const auto& w) { h.fill(vargs, bh::sample(sarray), bh::weight(w)); }), weight); } // for multi_cell template void fill_impl(bh::detail::accumulator_traits_holder&>, Histogram& h, const VArgs& vargs, const weight_t& weight, py::kwargs& kwargs) { boost::ignore_unused(weight); auto s = required_arg(kwargs, "sample"); finalize_args(kwargs); auto sarray = py::cast>(s); if(sarray.ndim() != 2) throw std::invalid_argument("Sample or weight array for MultiCell must be 2D"); auto buf = sarray.request(); // releasing gil here is safe, we don't manipulate refcounts const py::gil_scoped_release lock; const auto buf_shape0 = static_cast(buf.shape[0]); const auto buf_shape1 = static_cast(buf.shape[1]); auto* src = static_cast(buf.ptr); std::vector> vec_s; vec_s.reserve(buf_shape0); for(std::size_t i = 0; i < buf_shape0; i++) { vec_s.emplace_back(src + (i * buf_shape1), buf_shape1); } h.fill(vargs, bh::sample(vec_s)); } } // namespace detail template Histogram& fill(Histogram& self, const py::args& args, py::kwargs kwargs) { using value_type = typename Histogram::value_type; detail::fill_impl(bh::detail::accumulator_traits{}, self, detail::get_vargs(bh::unsafe_access::axes(self), args), detail::get_weight(kwargs), kwargs); return self; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/histogram.hpp000066400000000000000000000137131516627434300254570ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include namespace pybind11 { /// The descriptor for atomic_* is the same as the descriptor for *, as long this uses /// standard layout template struct format_descriptor> : format_descriptor { static_assert(std::is_standard_layout>::value, ""); }; } // namespace pybind11 namespace detail { template py::buffer_info make_buffer_impl(const Axes& axes, bool flow, T* ptr) { // strides are in bytes auto shape = bh::detail::make_stack_buffer(axes); auto strides = bh::detail::make_stack_buffer(axes); py::ssize_t stride = sizeof(T); unsigned rank = 0; char* start = reinterpret_cast(ptr); bh::detail::for_each_axis(axes, [&](const auto& axis) { const bool underflow = bh::axis::traits::options(axis) & bh::axis::option::underflow; if(!flow && underflow) start += stride; const auto extent = bh::axis::traits::extent(axis); shape[rank] = flow ? extent : axis.size(); strides[rank] = stride; stride *= extent; ++rank; }); return py::buffer_info( start, // Pointer to buffer sizeof(T), // Size of one scalar py::format_descriptor::format(), // Python format descriptor static_cast(rank), // Number of dimensions shape, // Buffer shape strides // Strides (in bytes) for each index ); } struct double_converter { template void operator()(T* tp, Buffer& b) const { b.template make(b.size, tp); } template void operator()(double*, Buffer&) const {} // nothing to do }; } // namespace detail /// Build and return a buffer over the current data. /// Flow controls whether under/over flow bins are present template py::buffer_info make_buffer(bh::histogram>& h, bool flow) { const auto& axes = bh::unsafe_access::axes(h); auto& storage = bh::unsafe_access::storage(h); return detail::make_buffer_impl(axes, flow, &storage[0]); } /// Specialization for unlimited_buffer template py::buffer_info make_buffer(bh::histogram>& h, bool flow) { const auto& axes = bh::unsafe_access::axes(h); auto& storage = bh::unsafe_access::storage(h); // User requested a view into the memory of unlimited storage. We convert // the internal storage to double now to avoid the view becoming invalid // upon changes to the histogram. This is the only way to provide a safe // view, because we cannot automatically update the view when the // underlying memory buffer changes. In practice it is ok, because users // usually want to get the view after filling the histogram, and then the // counts are usually converted to doubles for further processing anyway. auto& buffer = bh::unsafe_access::unlimited_storage_buffer(storage); buffer.visit(detail::double_converter(), buffer); return detail::make_buffer_impl(axes, flow, static_cast(buffer.ptr)); } /// From C++17 template constexpr std::add_const_t& std_as_const(T& t) noexcept { return t; } /// Specialization for multi_cell buffer template py::buffer_info make_buffer(bh::histogram>& h, bool flow) { const auto& axes = bh::unsafe_access::axes(h); auto& storage = bh::unsafe_access::storage(h); using AxesVec = std::decay_t; AxesVec new_axes; // Add the cells as a first pseudo-axis to treat them correctly for the buffer // creation. This will create a buffer in the shape (nelem, axis_1, axis_2, ...) // where nelem is the number of cells per bin This also coincides with how the // cells are stored on the multi cell storage side Having the cells as the // last dimension might feel more natural, but does not work with the current // storage implementation new_axes.emplace_back(axis::integer_none{0, static_cast(storage.nelem())}); new_axes.insert(std::end(new_axes), std::begin(axes), std::end(axes)); return detail::make_buffer_impl( std_as_const(new_axes), flow, static_cast(storage.get_buffer())); } /// Compute the bin of an array from a runtime list /// For example, [1,3,2] will return that bin of an array template const F& pyarray_at(const py::array_t& input, std::vector indexes) { const auto rank = static_cast(indexes.size()); if(input.ndim() != rank) throw py::value_error( "The input array dimensions must match the index dimensions"); const auto* strides = input.strides(); py::ssize_t flat_offset = 0; for(py::ssize_t r = 0; r < rank; r++) { auto input_shape = input.shape(r); py::ssize_t index = indexes[static_cast(r)]; if(index < 0 || index > input_shape) throw py::value_error("The index must not be outside the array boundaries"); flat_offset += (strides[r] / static_cast(sizeof(F))) * index; } return *(input.data() + flat_offset); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/kwargs.hpp000066400000000000000000000032511516627434300247540ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include /// Get and remove a value from a keyword argument dict template T required_arg(py::kwargs& kwargs, const char* name) { if(kwargs.contains(name)) { return py::cast(kwargs.attr("pop")(name)); } throw py::key_error(std::string(name) + " is required"); } /// Get and remove a value from a keyword argument dict or return None inline py::object optional_arg(py::kwargs& kwargs, const char* name) { if(kwargs.contains(name)) { return kwargs.attr("pop")(name); } return py::none(); } /// Get and remove a value from a keyword argument dict or return default value template T optional_arg(py::kwargs& kwargs, const char* name, T original_value) { if(kwargs.contains(name)) { return py::cast(kwargs.attr("pop")(name)); } return original_value; } /// Remove a value from a keyword argument dict if None, do not remove if non None /// (triggers final error) inline void none_only_arg(py::kwargs& kwargs, const char* name) { if(kwargs.contains(name)) { if(kwargs[name].is_none()) kwargs.attr("pop")(name); } } /// Run this last; it will provide an error if other keyword are still present inline void finalize_args(const py::kwargs& kwargs) { if(!kwargs.empty()) { auto keys = py::str(", ").attr("join")(kwargs.attr("keys")()); throw py::type_error(py::str("Keyword(s) {0} not expected").format(keys)); } } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/make_pickle.hpp000066400000000000000000000304401516627434300257220ustar00rootroot00000000000000// Copyright 2015-2019 Hans Dembinski and Henry Schreiner // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) // Design notes: This uses the Boost.Serialization mechanism without actually using the // Boost.Serialization library. The Boost mechanism is an algorithm to recursively break // down complex objects into primitives for saving and doing the reverse for loading. // When we save, we are building a tuple of Python primitives with this mechanism and // when we load, we are doing the reserve. Large buffers of primitive types are best // stored by converting them to numpy arrays. This way, we can ride piggy-back on // numpy's pickle code for fast loading and saving. // The Boost approach stores very little meta-data, only a version number. It relies on // the fact that the internal structure of the C++ classes is not changed. When you // actually change this internal structure, you must also increment this version // number and add code to the affected serialize method to load the previous version and // the new version. // // It is very important that there are specializations for all storages with non-trivial // accumulators. A histogram can have very very many cells and the generic serializer // converts each cell individually into Python objects which is extremely time // consuming. The storages should be specialized in the corresponding header file that // includes the storages, by providing a specialization of the save and load functions // // template // void save(Archive& ar, const MyClass& c, unsigned version); // // template // void load(Archive& ar, const MyClass& c, unsigned version); // // or for classes with template arguments, something like // // template // void save(Archive& ar, const MyClass& c, unsigned version); // // template // void load(Archive& ar, MyClass& c, unsigned version); // // in the global namespace. It should not be necessary to touch the code here. #pragma once #include #include #include #include #include #include #include // mp_or #include // mp_valid #include #include #include #include #include #include // Available as make_span in boost/core/make_span.hpp in 1.82+ namespace { template constexpr boost::span make_span(T* begin, std::size_t size) noexcept { return boost::span{begin, size}; } } // namespace template ().serialize(std::declval(), 0))> struct has_method_serialize_impl {}; template using has_method_serialize = typename boost::mp11::mp_valid::type; namespace boost { namespace serialization { // provide default implementation of boost::serialization::version template struct version : std::integral_constant {}; } // namespace serialization } // namespace boost template struct is_string : std::false_type {}; template struct is_string> : std::true_type {}; template struct is_string> : std::true_type {}; template using is_serialization_primitive = typename boost::mp11::mp_or, is_string>::type; template void save(Archive& ar, const T& t, unsigned version) { // default implementation calls serialize method const_cast(t).serialize(ar, version); } template void load(Archive& ar, T& t, unsigned version) { // default implementation calls serialize method static_assert(!static_cast(std::is_const::value), "T must be non-const"); t.serialize(ar, version); } template void split_serialize(std::true_type, Archive& ar, T& t, unsigned version) { load(ar, t, version); } template void split_serialize(std::false_type, Archive& ar, const T& t, unsigned version) { save(ar, t, version); } template void serialize(Archive& ar, T& t, unsigned version) { // default implementation calls serialize method static_assert(!static_cast(std::is_const::value), "T must be non-const"); split_serialize(typename Archive::is_loading{}, ar, t, version); } // builds a tuple of Python primitives from C++ primitives class tuple_oarchive { public: using is_saving = std::true_type; using is_loading = std::false_type; explicit tuple_oarchive(py::tuple& tup) : tup_(tup) {} template tuple_oarchive& operator&(boost::nvp t) { return operator<<(t.const_value()); } template tuple_oarchive& operator<<(boost::nvp t) { return operator<<(t.const_value()); } template tuple_oarchive& operator&(const T& t) { return operator<<(t); } template std::enable_if_t(is_serialization_primitive::value), tuple_oarchive&> operator<<(const T& t) { // no version number is saved for primitives this->operator<<(py::cast(t)); return *this; } template std::enable_if_t(is_serialization_primitive::value), tuple_oarchive&> operator<<(const T& t) { // save current class version with each non-primitive type const unsigned version = boost::serialization::version::value; this->operator<<(version); serialize(*this, const_cast(t), version); return *this; } tuple_oarchive& operator<<(const py::object& obj) { // maybe use growth factor 1.6 and shrink tuple to final size in destructor? tup_ = tup_ + py::make_tuple(obj); return *this; } tuple_oarchive& operator<<(py::object& obj) { return operator<<(static_cast(obj)); } tuple_oarchive& operator<<(py::object&& obj) { return operator<<(static_cast(obj)); } // put specializations here that side-step normal serialization // tuple_oarchive& operator<<(py::str& m) { // return operator<<(static_cast(m)); // } tuple_oarchive& operator<<(const py::str& m) { return operator<<(static_cast(m)); } tuple_oarchive& operator<<(const metadata_t& m) { return operator<<(static_cast(m)); } template tuple_oarchive& operator<<(const py::array_t& a) { return operator<<(static_cast(a)); } template std::enable_if_t(std::is_arithmetic::value), tuple_oarchive&> operator<<(const std::vector& v) { // fast version for vector of arithmetic types py::array_t const a(static_cast(v.size()), v.data()); this->operator<<(static_cast(a)); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_oarchive&> operator<<(const std::vector& v) { // generic version this->operator<<(v.size()); for(auto&& item : v) this->operator<<(item); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_oarchive&> operator<<(const bh::detail::array_wrapper& w) { // fast version py::array_t const a(static_cast(w.size), w.ptr); this->operator<<(static_cast(a)); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_oarchive&> operator<<(const bh::detail::array_wrapper& w) { // generic version for(auto&& item : ::make_span(w.ptr, w.size)) this->operator<<(item); return *this; } private: py::tuple& tup_; }; class tuple_iarchive { public: using is_saving = std::false_type; using is_loading = std::true_type; explicit tuple_iarchive(const py::tuple& t) : tup_(t) {} // no object tracking void reset_object_address(const void*, const void*) {}; template tuple_iarchive& operator&(boost::nvp t) { return operator>>(t.value()); } template tuple_iarchive& operator>>(boost::nvp t) { return operator>>(t.value()); } template tuple_iarchive& operator&(T& t) { return operator>>(t); } template std::enable_if_t(is_serialization_primitive::value), tuple_iarchive&> operator>>(T& t) { // no version number is saved for primitives py::object obj; this->operator>>(obj); t = py::cast(obj); return *this; } template std::enable_if_t(is_serialization_primitive::value), tuple_iarchive&> operator>>(T& t) { // load saved class version of each non-primitive type to call legacy code unsigned saved_version; this->operator>>(saved_version); serialize(*this, t, saved_version); return *this; } tuple_iarchive& operator>>(py::object& obj) { BOOST_ASSERT(cur_ < tup_.size()); obj = tup_[cur_++]; return *this; } // put specializations here that side-step normal serialization tuple_iarchive& operator>>(py::str& m) { return operator>>(static_cast(m)); } tuple_iarchive& operator>>(metadata_t& m) { return operator>>(static_cast(m)); } template tuple_iarchive& operator>>(py::array_t& a) { return operator>>(static_cast(a)); } template std::enable_if_t(std::is_arithmetic::value), tuple_iarchive&> operator>>(std::vector& v) { // fast version for vector of arithmetic types py::array_t a; this->operator>>(a); v.resize(static_cast(a.size())); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), v.begin()); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_iarchive&> operator>>(std::vector& v) { // generic version std::size_t new_size; this->operator>>(new_size); v.resize(new_size); for(auto&& item : v) this->operator>>(item); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_iarchive&> operator>>(bh::detail::array_wrapper& w) { // fast version py::array_t a; this->operator>>(a); // buffer wrapped by array_wrapper must already have correct size BOOST_ASSERT(static_cast(a.size()) == w.size); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), w.ptr); return *this; } template std::enable_if_t(std::is_arithmetic::value), tuple_iarchive&> operator>>(bh::detail::array_wrapper& w) { // generic version for(auto&& item : ::make_span(w.ptr, w.size)) this->operator>>(item); return *this; } private: const py::tuple& tup_; std::size_t cur_ = 0; }; /// Make a pickle serializer with a given type template decltype(auto) make_pickle() { return py::pickle( [](const T& obj) { py::tuple tup; tuple_oarchive oa{tup}; oa << obj; return tup; }, [](const py::tuple& tup) { tuple_iarchive ia{tup}; T obj; ia >> obj; return obj; }); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/metadata.hpp000066400000000000000000000012441516627434300252360ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include struct metadata_t : py::dict { PYBIND11_OBJECT(metadata_t, dict, PyDict_Check); using dict::dict; // default initialize to empty dict (must not be explicit) metadata_t() = default; bool operator==(const metadata_t& other) const { return py::dict::equal(other); } bool operator!=(const metadata_t& other) const { return py::dict::not_equal(other); } }; scikit-hep-boost-histogram-5f6f3e3/include/bh_python/multi_cell.hpp000066400000000000000000000210241516627434300256050ustar00rootroot00000000000000#ifndef BOOST_HISTOGRAM_MULTI_CELL_HPP #define BOOST_HISTOGRAM_MULTI_CELL_HPP #include #include #include #include #include #include #include namespace boost { namespace histogram { template struct multi_cell_base : public BASE { using BASE::BASE; template bool operator==(const S& values) const { if(values.size() != this->size()) return false; return std::equal(this->begin(), this->end(), values.begin()); } template bool operator!=(const S& values) const { return !operator==(values); } }; template struct multi_cell_reference : public multi_cell_base> { // using boost::span::span; using multi_cell_base>::multi_cell_base; void operator()(const boost::span values) { operator+=(values); } void operator+=(const boost::span values) { if(values.size() != this->size()) throw std::range_error("size does not match for += ref"); auto it = this->begin(); for(const T& x : values) *it++ += x; } template multi_cell_reference& operator=(const S& values) { if(values.size() != this->size()) throw std::range_error("size does not match for = ref"); auto it = this->begin(); for(const T& x : values) *it++ = x; return *this; } }; template struct multi_cell_value : public multi_cell_base> { using multi_cell_base>::multi_cell_base; explicit multi_cell_value(const boost::span values) { this->assign(values.begin(), values.end()); } multi_cell_value() = default; void operator()(const boost::span& values) { operator+=(values); } void operator+=(const boost::span& values) { if(values.size() != this->size()) { if(this->size() > 0) { throw std::range_error("size does not match for += val"); } this->assign(values.begin(), values.end()); return; } auto it = this->begin(); for(const T& x : values) *it++ += x; } template multi_cell_value& operator=(const S values) { this->assign(values.begin(), values.end()); return *this; } }; template class multi_cell { public: using element_type = ElementType; using value_type = multi_cell_value; using reference = multi_cell_reference; using const_reference = const reference; template struct iterator_base : public detail::iterator_adaptor, std::size_t, Reference> { using base_type = detail::iterator_adaptor, std::size_t, Reference>; iterator_base() = default; iterator_base(const iterator_base& other) : iterator_base(other.par_, other.base()) {} iterator_base(MWPtr par, std::size_t idx) : base_type{idx} , par_{par} {} iterator_base& operator=(const iterator_base& other) = default; decltype(auto) operator*() const { return Reference{par_->buffer_.get() + this->base() * par_->nelem_, par_->nelem_}; } MWPtr par_ = nullptr; }; using iterator = iterator_base; using const_iterator = iterator_base; static constexpr bool has_threading_support() { return false; } explicit multi_cell(const std::size_t k = 0) : nelem_{k} {} multi_cell(const multi_cell& other) { *this = other; } multi_cell& operator=(const multi_cell& other) { // Protect against self assignment if(this == &other) { return *this; } nelem_ = other.nelem_; reset(other.size_); std::copy( other.buffer_.get(), other.buffer_.get() + size_ * nelem_, buffer_.get()); return *this; } std::size_t size() const { return size_; } std::size_t nelem() const { return nelem_; } void reset(std::size_t n) { size_ = n; buffer_.reset(new element_type[size_ * nelem_]); default_fill(); } void reset_nelem(std::size_t n) { nelem_ = n; reset(size_); } template ::value, bool> = true> void default_fill() {} template ::value, bool> = true> void default_fill() { std::fill_n(buffer_.get(), size_ * nelem_, 0); } iterator begin() { return {this, 0}; } iterator end() { return {this, size_}; } const_iterator begin() const { return {this, 0}; } const_iterator end() const { return {this, size_}; } reference operator[](std::size_t i) { return reference{buffer_.get() + i * nelem_, nelem_}; } const_reference operator[](std::size_t i) const { return const_reference{buffer_.get() + i * nelem_, nelem_}; } template bool operator==(const multi_cell& other) const { if(size_ * nelem_ != other.size_ * other.nelem_) return false; return std::equal( buffer_.get(), buffer_.get() + size_ * nelem_, other.buffer_.get()); } template bool operator!=(const multi_cell& other) const { return !operator==(other); } template void operator+=(const multi_cell& other) { if(size_ * nelem_ != other.size_ * other.nelem_) { throw std::range_error("size does not match"); } for(std::size_t i = 0; i < size_ * nelem_; i++) { buffer_[i] += other.buffer_[i]; } } template void serialize(Archive& ar, unsigned /* version */) { ar& make_nvp("size", size_); ar& make_nvp("nelem", nelem_); std::vector w; if(Archive::is_loading::value) { ar& make_nvp("buffer", w); reset(size_); std::swap_ranges(buffer_.get(), buffer_.get() + size_ * nelem_, w.data()); } else { w.assign(buffer_.get(), buffer_.get() + size_ * nelem_); ar& make_nvp("buffer", w); } } element_type* get_buffer() { return buffer_.get(); } private: std::size_t size_ = 0; // Number of bins std::size_t nelem_ = 0; // Number of cells per bin std::unique_ptr buffer_; }; template std::ostream& operator<<(std::ostream& os, const multi_cell_value& v) { os << "multi_cell_value("; bool first = true; for(const T& x : v) if(first) { first = false; os << x; } else os << ", " << x; os << ")"; return os; } template std::ostream& operator<<(std::ostream& os, const multi_cell_reference& v) { os << "multi_cell_reference("; bool first = true; for(const T& x : v) if(first) { first = false; os << x; } else os << ", " << x; os << ")"; return os; } template std::ostream& operator<<(std::ostream& os, const multi_cell& v) { os << "multi_cell(\n"; int index = 0; for(const multi_cell_reference& x : v) { os << "Index " << index << ": " << x << "\n"; index++; } os << ")"; return os; } /** * Overload the make_default_impl function to default initialize multi cell storage * with the number of cells per bin from the old storage that is used as a reference. * Otherwise, the number of cells would not be set and subsequent operations with the * new storage would fail as it would be default initialized with 0 cells per bin. */ template multi_cell make_default_impl(const multi_cell& old_storage, float) { using S = multi_cell; return S{old_storage.nelem()}; } } // namespace histogram } // namespace boost #endif scikit-hep-boost-histogram-5f6f3e3/include/bh_python/overload.hpp000066400000000000000000000015021516627434300252660ustar00rootroot00000000000000// Copyright 2018-2019 Hans Dembinski and Henry Schreiner // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include // for std::forward template struct overload_t; template struct overload_t : F { explicit overload_t(F&& f) : F(std::forward(f)) {} using F::operator(); }; template struct overload_t : F, overload_t { explicit overload_t(F&& x, Fs&&... xs) : F(std::forward(x)) , overload_t(std::forward(xs)...) {} using F::operator(); using overload_t::operator(); }; template auto overload(Fs&&... xs) { return overload_t(std::forward(xs)...); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/pybind11.hpp000066400000000000000000000042121516627434300251030ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. // This file must be the first include (that is, it must be before stdlib or boost // headers) This reduces the warning output from Python.h and also lets us define s afew // things #pragma once // TODO: restructure metadata_t to hold a py::dict instead of subclass it, add locks #define PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF #include #include #include #include #include #include #include namespace py = pybind11; using namespace pybind11::literals; // For ""_a syntax namespace boost { namespace histogram {} } // namespace boost namespace bh = boost::histogram; /// Static if standin: define a method if expression is true template void def_optionally(T&& module, std::true_type, Args&&... expression) { module.def(std::forward(expression...)); } /// Static if standin: Do nothing if compile time expression is false template void def_optionally(T&&, std::false_type, Args&&...) {} /// Shift to string template std::string shift_to_string(const T& x) { std::ostringstream out; out << x; return out.str(); } template void unchecked_set_impl(std::true_type, py::tuple& tup, py::ssize_t i, Obj&& obj) { // PyTuple_SetItem steals a reference to 'val' if(PyTuple_SetItem(tup.ptr(), i, obj.release().ptr()) != 0) { throw py::error_already_set(); } } template void unchecked_set_impl(std::false_type, py::tuple& tup, py::ssize_t i, T&& t) { unchecked_set_impl(std::true_type{}, tup, i, py::cast(std::forward(t))); } /// Unchecked tuple assign template void unchecked_set(py::tuple& tup, std::size_t i, T&& t) { unchecked_set_impl(std::is_base_of>{}, tup, static_cast(i), std::forward(t)); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/register_accumulator.hpp000066400000000000000000000046351516627434300277100ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include // This can be included here since we don't include Boost.Histogram's ostream namespace boost { namespace histogram { namespace accumulators { template std::basic_ostream& operator<<(std::basic_ostream& os, const sum& x) { if(os.width() == 0) return os << x.large_part() << " + " << x.small_part(); return handle_nonzero_width(os, x); } } // namespace accumulators } // namespace histogram } // namespace boost template py::class_ register_accumulator(py::module acc, Args&&... args) { return py::class_(std::move(acc), std::forward(args)...) .def(py::init<>()) .def(py::self += py::self) .def("__eq__", [](const A& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const A& self, const py::object& other) { try { return self != py::cast(other); } catch(const py::cast_error&) { return true; } }) .def(py::self *= double()) .def("__add__", [](const A& self, const A& other) { A retval(self); retval += other; return retval; }) // The c++ name is replaced with the Python name here .def("__repr__", [](const py::object& self) { const A& item = py::cast(self); py::str const str = shift_to_string(item); return py::str("{0.__class__.__name__}({1})").format(self, str); }) .def("__copy__", [](const A& self) { return A(self); }) .def("__deepcopy__", [](const A& self, const py::object&) { return A(self); }) .def(make_pickle()); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/register_axis.hpp000066400000000000000000000225011516627434300263250ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cpp_noexcept_function_type #define BHP_NOEXCEPT_17 noexcept #else #define BHP_NOEXCEPT_17 #endif template auto vectorize_index(T input) { return py::vectorize(input); } #ifdef __cpp_noexcept_function_type #define BHP_NOEXCEPT_17 noexcept #else #define BHP_NOEXCEPT_17 #endif namespace detail { template decltype(auto) axis_cast(py::handle x) { return special_cast(x); } template <> inline decltype(auto) axis_cast(py::handle x) { if(py::isinstance(x)) return py::cast(x); auto val = py::cast(x); auto ival = static_cast(val); if(static_cast(ival) == val) return ival; throw py::type_error(py::str("cannot cast {} to int").format(val)); } } // namespace detail // we overload vectorize index for category axis template auto vectorize_index(int (bh::axis::category::*pindex)(const T&) const BHP_NOEXCEPT_17) { return [pindex](const bh::axis::category& self, const py::object& arg) -> py::object { auto index = std::mem_fn(pindex); if(detail::is_value(arg)) { auto index_value = index(self, detail::axis_cast(arg)); if(index_value >= self.size()) throw pybind11::key_error(py::str("{!r} not in axis").format(arg)); return py::cast(index_value); } auto indices = array_like(arg); auto values = detail::special_cast>(arg); auto* ip = indices.mutable_data(); auto vp = values.data(); for(std::size_t i = 0, n = values.size(); i < n; ++i) { ip[i] = index(self, vp[i]); if(ip[i] >= self.size()) throw pybind11::key_error(py::str("{!r} not in axis").format(vp[i])); } return indices; }; } template auto vectorize_value(T input) { return py::vectorize(input); } // we overload vectorize value for category axis template auto vectorize_value(R (bh::axis::category::*pvalue)(int) const) { return [pvalue](const bh::axis::category& self, const py::object& arg) -> py::object { auto value = std::mem_fn(pvalue); if(detail::is_value(arg)) { auto i = py::cast(arg); return i < self.size() ? py::cast(value(self, i)) : py::none(); } auto indices = py::cast>(arg); // this limitation could be removed if we find a way to make object arrays if(indices.ndim() != 1) throw std::invalid_argument("only ndim == 1 supported"); const auto n = static_cast(indices.size()); py::tuple values(n); const auto* pi = indices.data(); for(std::size_t k = 0; k < n; ++k) { const auto i = pi[k]; unchecked_set( values, k, i < self.size() ? py::cast(value(self, i)) : py::none()); } return values; }; } #undef BHP_NOEXCEPT_17 /// Add helpers common to all axis types template py::class_ register_axis(py::module& m, Args&&... args) { py::class_ ax(m, axis::string_name(), std::forward(args)...); ax.def("__repr__", &shift_to_string) .def("__eq__", [](const A& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const A& self, const py::object& other) { try { return self != py::cast(other); } catch(const py::cast_error&) { return true; } }) .def_property_readonly("traits_underflow", [](const A& self) { return static_cast( self.options() & bh::axis::option::underflow); }) .def_property_readonly("traits_overflow", [](const A& self) { return static_cast( self.options() & bh::axis::option::overflow); }) .def_property_readonly("traits_circular", [](const A& self) { return static_cast( self.options() & bh::axis::option::circular); }) .def_property_readonly("traits_growth", [](const A& self) { return static_cast(self.options() & bh::axis::option::growth); }) .def_property_readonly( "traits_continuous", [](const A& self) { return bh::axis::traits::continuous(self); }) .def_property_readonly( "traits_ordered", [](const A& self) { return bh::axis::traits::ordered(self); }) .def_property( "raw_metadata", [](const A& self) { return self.metadata(); }, [](A& self, const metadata_t& label) { self.metadata() = label; }, "Set the metadata") .def_property_readonly( "size", [](const A& ob) { return ob.size(); }, "Returns the number of bins excluding under- and overflow") .def_property_readonly( "extent", &bh::axis::traits::extent, "Returns the number of bins including under- and overflow") .def("__copy__", [](const A& self) { return A(self); }) .def("__deepcopy__", [](const A& self, const py::object& memo) { A* a = new A(self); py::module const copy = py::module::import("copy"); a->metadata() = copy.attr("deepcopy")(a->metadata(), memo); return a; }) .def( "bin", [](const A& ax, int i) { const bh::axis::index_type begin = bh::axis::traits::get_options::test( bh::axis::option::underflow) ? -1 : 0; const bh::axis::index_type end = bh::axis::traits::get_options::test(bh::axis::option::overflow) ? ax.size() + 1 : ax.size(); if(begin <= i && i < end) return axis::unchecked_bin(ax, i); throw py::index_error(); }, "i"_a, "Return bin at index (-1 accesses underflow bin, size access overflow)") .def( "__iter__", [](const A& ax) { struct iterator : bh::detail::iterator_adaptor { const A& axis_; iterator(const A& axis, int idx) : iterator::iterator_adaptor_(idx) , axis_(axis) {} auto operator*() const { return axis::unchecked_bin(axis_, this->base()); } }; iterator begin(ax, 0); iterator end(ax, ax.size()); return py::make_iterator(std::move(begin), std::move(end)); }, py::keep_alive<0, 1>()) // This is a property because we hide the flow bins consistently here. // Users should use histogram.to_numpy(True) to get a consistent representation // of edges and the cell matrix, where we can guarantee that both are in sync. .def_property_readonly( "edges", [](const A& ax) { return axis::edges(ax, false); }, "Return bin edges") .def_property_readonly("centers", &axis::centers, "Return bin centers") .def_property_readonly("widths", &axis::widths, "Return bin widths") .def("index", vectorize_index(&A::index), "Index for value (or values) on the axis") .def("value", vectorize_value(&A::value), "Value at index (or indices)") .def(make_pickle()); return ax; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/register_histogram.hpp000066400000000000000000000345751516627434300273740ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include template auto register_histogram(py::module& m, const char* name, const char* desc) { using histogram_t = bh::histogram; using value_type = typename histogram_t::value_type; py::class_ hist(m, name, desc, py::buffer_protocol()); hist.def(py::init(), "axes"_a, "storage"_a = S()) .def_buffer( [](histogram_t& h) -> py::buffer_info { return make_buffer(h, false); }) .def("rank", &histogram_t::rank) .def("size", &histogram_t::size) .def("reset", &histogram_t::reset) .def("__copy__", [](const histogram_t& self) { return histogram_t(self); }) .def("__deepcopy__", [](const histogram_t& self, const py::object& memo) { auto* a = new histogram_t(self); py::module const copy = py::module::import("copy"); for(unsigned i = 0; i < a->rank(); i++) { bh::unsafe_access::axis(*a, i).metadata() = copy.attr("deepcopy")(a->axis(i).metadata(), memo); } return a; }) .def(py::self += py::self) .def("__eq__", [](const histogram_t& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const histogram_t& self, const py::object& other) { try { return self != py::cast(other); } catch(const py::cast_error&) { return true; } }) .def_property_readonly_static( "_storage_type", [](const py::object&) { return py::type::of(); }) ; // Protection against an overzealous warning system // https://bugs.llvm.org/show_bug.cgi?id=43124 #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wself-assign-overloaded" #endif def_optionally(hist, bh::detail::has_operator_rdiv{}, py::self /= py::self); def_optionally(hist, bh::detail::has_operator_rmul{}, py::self *= py::self); def_optionally(hist, bh::detail::has_operator_rsub{}, py::self -= py::self); #ifdef __clang__ #pragma GCC diagnostic pop #endif hist.def( "to_numpy", [](histogram_t& h, bool flow) { py::tuple tup(1 + h.rank()); // Add the histogram buffer as the first argument unchecked_set(tup, 0, py::array(make_buffer(h, flow))); // Add the axis edges h.for_each_axis([&tup, flow, i = 0U](const auto& ax) mutable { unchecked_set(tup, ++i, axis::edges(ax, flow, true)); }); return tup; }, "flow"_a = false) .def( "view", [](const py::object& self, bool flow) { auto& h = py::cast(self); return py::array(make_buffer(h, flow), self); }, "flow"_a = false) .def( "axis", [](const histogram_t& self, int i) -> py::object { unsigned const ii = i < 0 ? self.rank() - static_cast(std::abs(i)) : static_cast(i); if(ii < self.rank()) { const axis_variant& var = self.axis(ii); return bh::axis::visit( [](auto&& item) -> py::object { // Here we return a new, no-copy py::object that // is not yet tied to the histogram. py::keep_alive // is needed to make sure the histogram is alive as long // as the axes references are. return py::cast(item, py::return_value_policy::reference); }, var); } throw std::out_of_range("The axis value must be less than the rank"); }, "i"_a = 0, py::keep_alive<0, 1>()) .def("at", [](const histogram_t& self, const py::args& args) -> value_type { auto int_args = py::cast>(args); return self.at(int_args); }) .def("_at_set", [](histogram_t& self, const value_type& input, const py::args& args) { auto int_args = py::cast>(args); self.at(int_args) = input; }) .def("__repr__", &shift_to_string) .def( "sum", [](const histogram_t& self, bool flow) { const py::gil_scoped_release release; return bh::algorithm::sum( self, flow ? bh::coverage::all : bh::coverage::inner); }, "flow"_a = false) .def( "empty", [](const histogram_t& self, bool flow) { const py::gil_scoped_release release; return bh::algorithm::empty( self, flow ? bh::coverage::all : bh::coverage::inner); }, "flow"_a = false) .def("reduce", [](const histogram_t& self, const py::args& args) { auto commands = py::cast>(args); const py::gil_scoped_release release; return bh::algorithm::reduce(self, commands); }) .def("project", [](const histogram_t& self, const py::args& values) { auto cpp_values = py::cast>(values); const py::gil_scoped_release release; return bh::algorithm::project(self, cpp_values); }) .def("fill", &fill) .def(make_pickle()) ; return hist; } template <> auto inline register_histogram>(py::module& m, const char* name, const char* desc) { using S = bh::multi_cell; using histogram_t = bh::histogram; using value_type = std::vector; py::class_ hist(m, name, desc, py::buffer_protocol()); hist.def(py::init(), "axes"_a, "storage"_a = S()) .def_buffer( [](histogram_t& h) -> py::buffer_info { return make_buffer(h, false); }) .def("rank", &histogram_t::rank) .def("size", &histogram_t::size) .def("nelem", [](const histogram_t& self) { return bh::unsafe_access::storage(self).nelem(); }) .def("reset", &histogram_t::reset) // Reset number of cells per bin after recreation of histogram because number // of cells can (?) not be passed to the creation of the new histogram. Set it // manually afterwards. .def("reset_nelem", [](histogram_t& self, const std::size_t nelem) { bh::unsafe_access::storage(self).reset_nelem(nelem); }) .def("__copy__", [](const histogram_t& self) { return histogram_t(self); }) .def("__deepcopy__", [](const histogram_t& self, const py::object& memo) { auto* a = new histogram_t(self); const py::module copy = py::module::import("copy"); for(unsigned i = 0; i < a->rank(); i++) { bh::unsafe_access::axis(*a, i).metadata() = copy.attr("deepcopy")(a->axis(i).metadata(), memo); } return a; }) .def(py::self += py::self) .def("__eq__", [](const histogram_t& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const histogram_t& self, const py::object& other) { try { return self != py::cast(other); } catch(const py::cast_error&) { return true; } }) .def_property_readonly_static( "_storage_type", [](const py::object&) { return py::type::of(); }) ; // Protection against an overzealous warning system // https://bugs.llvm.org/show_bug.cgi?id=43124 #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wself-assign-overloaded" #endif def_optionally(hist, bh::detail::has_operator_rdiv{}, py::self /= py::self); def_optionally(hist, bh::detail::has_operator_rmul{}, py::self *= py::self); def_optionally(hist, bh::detail::has_operator_rsub{}, py::self -= py::self); #ifdef __clang__ #pragma GCC diagnostic pop #endif hist.def( "to_numpy", [](histogram_t& h, bool flow) { py::tuple tup(1 + h.rank()); // Add the histogram buffer as the first argument unchecked_set(tup, 0, py::array(make_buffer(h, flow))); // Add the axis edges h.for_each_axis([&tup, flow, i = 0U](const auto& ax) mutable { unchecked_set(tup, ++i, axis::edges(ax, flow, true)); }); return tup; }, "flow"_a = false) .def( "view", [](const py::object& self, bool flow) { auto& h = py::cast(self); return py::array(make_buffer(h, flow), self); }, "flow"_a = false) .def( "axis", [](const histogram_t& self, int i) -> py::object { unsigned const ii = i < 0 ? self.rank() - static_cast(std::abs(i)) : static_cast(i); if(ii < self.rank()) { const axis_variant& var = self.axis(ii); return bh::axis::visit( [](auto&& item) -> py::object { // Here we return a new, no-copy py::object that // is not yet tied to the histogram. py::keep_alive // is needed to make sure the histogram is alive as long // as the axes references are. return py::cast(item, py::return_value_policy::reference); }, var); } throw std::out_of_range("The axis value must be less than the rank"); }, "i"_a = 0, py::keep_alive<0, 1>()) .def("at", [](const histogram_t& self, const py::args& args) -> value_type { auto int_args = py::cast>(args); auto at_value = self.at(int_args); return {at_value.begin(), at_value.end()}; }) .def("_at_set", [](histogram_t& self, const value_type& input, const py::args& args) { auto int_args = py::cast>(args); self.at(int_args) = input; }) .def("__repr__", &shift_to_string) .def( "sum", [](const histogram_t& self, bool flow) -> value_type { const py::gil_scoped_release release; return bh::algorithm::sum( self, flow ? bh::coverage::all : bh::coverage::inner); }, "flow"_a = false) .def( "empty", [](const histogram_t& self, bool flow) { const py::gil_scoped_release release; return bh::algorithm::empty( self, flow ? bh::coverage::all : bh::coverage::inner); }, "flow"_a = false) .def("reduce", [](const histogram_t& self, const py::args& args) { auto commands = py::cast>(args); const py::gil_scoped_release release; return bh::algorithm::reduce(self, commands); }) .def("project", [](const histogram_t& self, const py::args& values) { auto cpp_values = py::cast>(values); const py::gil_scoped_release release; return bh::algorithm::project(self, cpp_values); }) .def("fill", &fill) .def(make_pickle()) ; return hist; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/register_storage.hpp000066400000000000000000000073001516627434300270250ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include /// Add helpers common to all storage types template py::class_ register_storage(py::module& m, const char* name, const char* desc) { py::class_ storage(m, name, desc); storage.def(py::init<>()) .def("__eq__", [](const A& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const A& self, const py::object& other) { try { return self != py::cast(other); } catch(const py::cast_error&) { return true; } }) .def(make_pickle()) .def("__copy__", [](const A& self) { return A(self); }) .def("__deepcopy__", [](const A& self, const py::object&) { return A(self); }); return storage; } /// Add helpers to the unlimited storage type template <> py::class_ inline register_storage(py::module& m, const char* name, const char* desc) { using A = storage::unlimited; // match code above py::class_ storage(m, name, desc); storage.def(py::init<>()) .def("__eq__", [](const A& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const A& self, const py::object& other) { try { return !(self == py::cast(other)); } catch(const py::cast_error&) { return true; } }) .def(make_pickle()) .def("__copy__", [](const A& self) { return A(self); }) .def("__deepcopy__", [](const A& self, const py::object&) { return A(self); }); return storage; } /// Add helpers to the multi_cell storage type template <> py::class_ inline register_storage(py::module& m, const char* name, const char* desc) { using A = storage::multi_cell; // match code above py::class_ storage(m, name, desc); storage.def(py::init(), py::arg("k") = 0) .def("__eq__", [](const A& self, const py::object& other) { try { return self == py::cast(other); } catch(const py::cast_error&) { return false; } }) .def("__ne__", [](const A& self, const py::object& other) { try { return !(self == py::cast(other)); } catch(const py::cast_error&) { return true; } }) .def(make_pickle()) .def("__copy__", [](const A& self) { return A(self); }) .def("__deepcopy__", [](const A& self, const py::object&) { return A(self); }) .def_property_readonly("nelem", [](const A& self) { return self.nelem(); }) ; return storage; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/regular_numpy.hpp000066400000000000000000000025211516627434300263460ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include namespace bh = boost::histogram; namespace axis { using index_type = boost::histogram::axis::index_type; /// Mimics the numpy behavior for the upper bin; required for auto range. class regular_numpy : public bh::axis::regular { using value_type = double; double stop_{0}; public: regular_numpy(unsigned n, value_type start, value_type stop, metadata_t meta = {}) : regular(n, start, stop, std::move(meta)) , stop_(stop) {} regular_numpy(const regular_numpy& a, index_type i, index_type j, unsigned n) : regular(a, i, j, n) , stop_(a.stop_) {} regular_numpy() = default; bh::axis::index_type index(value_type v) const { return v <= stop_ ? std::min(regular::index(v), size() - 1) : regular::index(v); } template void serialize(Archive& ar, unsigned version) { regular::serialize(ar, version); ar& boost::make_nvp("stop", stop_); } }; } // namespace axis scikit-hep-boost-histogram-5f6f3e3/include/bh_python/storage.hpp000066400000000000000000000156021516627434300251250ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace storage { // Names match Python names using int64 = bh::dense_storage; using atomic_int64 = bh::dense_storage>; using double_ = bh::dense_storage; using unlimited = bh::unlimited_storage<>; using weight = bh::dense_storage>; using multi_cell = bh::multi_cell; using mean = bh::dense_storage>; using weighted_mean = bh::dense_storage>; // Allow repr to show python name template inline const char* name() { return "unknown"; } template <> inline const char* name() { return "int64"; } template <> inline const char* name() { return "atomic_int64"; } template <> inline const char* name() { return "double"; } template <> inline const char* name() { return "unlimited"; } template <> inline const char* name() { return "weight"; } template <> inline const char* name() { return "multi_cell"; } template <> inline const char* name() { return "mean"; } template <> inline const char* name() { return "weighted_mean"; } } // namespace storage // It is very important that storages with accumulators have specialized serialization. // No specializations needed for dense_storage or unlimited_storage. template void save(Archive& ar, const storage::atomic_int64& s, unsigned /* version */) { // We cannot view the memory as a numpy array, because the internal layout of // std::atomic is undefined. So no reinterpret_casts are allowed. py::array_t a(static_cast(s.size())); auto in_ptr = s.begin(); auto* out_ptr = a.mutable_data(); for(; in_ptr != s.end(); ++in_ptr, ++out_ptr) *out_ptr = in_ptr->value(); ar << a; } template void load(Archive& ar, storage::atomic_int64& s, unsigned /* version */) { // data is stored as flat numpy array py::array_t a; ar >> a; s.resize(static_cast(a.size())); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), s.data()); } template void save(Archive& ar, const bh::dense_storage>& s, unsigned /* version */) { using T = accumulators::weighted_sum; static_assert(std::is_standard_layout::value && std::is_trivially_copyable::value && sizeof(T) == 2 * sizeof(double), "weighted_sum cannot be fast serialized"); // view storage buffer as flat numpy array py::array_t const a(static_cast(s.size()) * 2, reinterpret_cast(s.data())); ar << a; } template void load(Archive& ar, bh::dense_storage>& s, unsigned /* version */) { // data is stored as flat numpy array py::array_t a; ar >> a; s.resize(static_cast(a.size() / 2)); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), reinterpret_cast(s.data())); } template void save(Archive& ar, const bh::dense_storage>& s, unsigned /* version */) { using T = accumulators::mean; static_assert(std::is_standard_layout::value && std::is_trivially_copyable::value && sizeof(T) == 3 * sizeof(double), "mean cannot be fast serialized"); // view storage buffer as flat numpy array py::array_t const a(static_cast(s.size()) * 3, reinterpret_cast(s.data())); ar << a; } template void load(Archive& ar, bh::dense_storage>& s, unsigned /* version */) { // data is stored as flat numpy array py::array_t a; ar >> a; s.resize(static_cast(a.size() / 3)); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), reinterpret_cast(s.data())); } template void save(Archive& ar, const bh::dense_storage>& s, unsigned /* version */) { using T = accumulators::weighted_mean; static_assert(std::is_standard_layout::value && std::is_trivially_copyable::value && sizeof(T) == 4 * sizeof(double), "weighted_mean cannot be fast serialized"); // view storage buffer as flat numpy array py::array_t const a(static_cast(s.size()) * 4, reinterpret_cast(s.data())); ar << a; } template void load(Archive& ar, bh::dense_storage>& s, unsigned /* version */) { // data is stored as flat numpy array py::array_t a; ar >> a; s.resize(static_cast(a.size() / 4)); // sadly we cannot move the memory from the numpy array into the vector std::copy(a.data(), a.data() + a.size(), reinterpret_cast(s.data())); } namespace pybind11 { namespace detail { /// Allow a Python int to implicitly convert to an atomic int in C++ template <> struct type_caster { PYBIND11_TYPE_CASTER(storage::atomic_int64::value_type, _("atomic_int64")); bool load(handle src, bool) { auto* ptr = PyNumber_Long(src.ptr()); if(ptr == nullptr) return false; value = PyLong_AsLongLong(ptr); Py_DECREF(ptr); return PyErr_Occurred() == nullptr; } static handle cast(const storage::atomic_int64::value_type& src, return_value_policy /* policy */, handle /* parent */) { return PyLong_FromLongLong(src.value()); } }; } // namespace detail } // namespace pybind11 scikit-hep-boost-histogram-5f6f3e3/include/bh_python/transform.hpp000066400000000000000000000145131516627434300254740ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include #include namespace bh = boost::histogram; struct func_transform { using raw_t = double(double); raw_t* _forward = nullptr; raw_t* _inverse = nullptr; py::object _forward_ob; // Held for reference counting, repr, and pickling py::object _inverse_ob; py::object _forward_converted; // Held for reference counting if conversion makes a // new object (ctypes does not bump the refcount) py::object _inverse_converted; py::object _convert_ob; // Called before computing transform if not None py::str _name; // Optional name (uses repr from objects otherwise) /// Convert an object into a std::function. Can handle ctypes /// function pointers and pybind11 C++ functions, or anything /// else with a defined convert function std::tuple compute(py::object& input) { // Run the conversion function on the input (unless conversion is None) py::object const tmp_src = _convert_ob.is_none() ? input : _convert_ob(input); // If a CTypes object is present, just use that (numba, for example) py::object const src = py::getattr(tmp_src, "ctypes", tmp_src); // import ctypes py::module const ctypes = py::module::import("ctypes"); // Get the type: double(double) // function_type = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) py::handle const CFUNCTYPE = ctypes.attr("CFUNCTYPE"); py::handle const c_double = ctypes.attr("c_double"); py::object const function_type = CFUNCTYPE(c_double, c_double); if(py::isinstance(src, function_type)) { py::handle const cast = ctypes.attr("cast"); py::handle const c_void_p = ctypes.attr("c_void_p"); // ctypes.cast(in, ctypes.c_void_p).value py::object const addr_obj = cast(src, c_void_p); auto addr = py::cast(addr_obj.attr("value")); auto ptr = reinterpret_cast(addr); // NOLINT(performance-no-int-to-ptr) return std::make_tuple(ptr, src); } // If we made it to this point, we probably have a C++ pybind object or an // invalid object. The following is based on the std::function conversion in // pybind11/functional.hpp if(!py::isinstance(src)) throw py::type_error("Only ctypes double(double) and C++ functions allowed " "(must be function)"); py::detail::make_caster> func_caster; if(!func_caster.load(src, /*convert*/ false)) { // Note that each error is slightly different just to help with debugging throw py::type_error("Only ctypes double(double) and C++ functions allowed " "(must be stateless)"); } auto func = static_cast&>(func_caster); auto* cfunc = func.target(); if(cfunc == nullptr) { throw py::type_error( "Retrieving double(double) function failed (must be stateless)"); } return std::make_tuple(*cfunc, src); } func_transform(py::object f, py::object i, py::object c, py::str n) : _forward_ob(f) , _inverse_ob(i) , _convert_ob(std::move(c)) , _name(std::move(n)) { std::tie(_forward, _forward_converted) = compute(f); std::tie(_inverse, _inverse_converted) = compute(i); } func_transform() = default; ~func_transform() = default; func_transform(const func_transform&) = default; func_transform(func_transform&&) noexcept = default; func_transform& operator=(const func_transform&) = default; func_transform& operator=(func_transform&&) noexcept = default; double forward(double x) const { return _forward(x); } double inverse(double x) const { return _inverse(x); } bool operator==(const func_transform& other) const noexcept { try { return _forward_ob.equal(other._forward_ob) && _inverse_ob.equal(other._inverse_ob); } catch(const py::error_already_set&) { return false; } } template void serialize(Archive& ar, unsigned /* version */) { ar& boost::make_nvp("forward", _forward_ob); ar& boost::make_nvp("inverse", _inverse_ob); ar& boost::make_nvp("convert", _convert_ob); ar& boost::make_nvp("name", _name); if(Archive::is_loading::value) { std::tie(_forward, _forward_converted) = compute(_forward_ob); std::tie(_inverse, _inverse_converted) = compute(_inverse_ob); } } }; namespace boost { namespace histogram { namespace detail { inline const char* axis_suffix(const ::func_transform&) { return "_trans"; } } // namespace detail } // namespace histogram } // namespace boost /// Simple deep copy for any class *without* a python component template T deep_copy(const T& input, py::object&) { return T(input); } /// Specialization for the case where Python components are present /// (Function transform in this case) template <> inline func_transform deep_copy(const func_transform& input, py::object& memo) { py::module const copy = py::module::import("copy"); py::object const forward = copy.attr("deepcopy")(input._forward_ob, memo); py::object const inverse = copy.attr("deepcopy")(input._inverse_ob, memo); py::object const convert = copy.attr("deepcopy")(input._convert_ob, memo); py::str const name = copy.attr("deepcopy")(input._name, memo); return {forward, inverse, convert, name}; } // Print in repr template std::basic_ostream& operator<<(std::basic_ostream& os, const func_transform&) { return os << "func_transform"; } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/try_cast.hpp000066400000000000000000000034171516627434300253120ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include // end of recursion template R try_cast_impl(boost::mp11::mp_list<>, py::object, Unary&&) { throw py::cast_error("try_cast failed to find a match for object argument"); } // recursion template R try_cast_impl(boost::mp11::mp_list, py::object obj, Unary&& unary) { if(py::isinstance(obj)) return unary(py::cast(obj)); return try_cast_impl( boost::mp11::mp_list{}, obj, std::forward(unary)); } /** Cast python object to first match in type list and run functor with that type. Returns whatever the functor returns. The functor return type must not depend on the argument type. In other words, all functor overloads must return the same type. Throws pybind11::cast_error if no match is found. */ template decltype(auto) try_cast_over(py::object obj, Unary&& unary) { using R = decltype(unary(std::declval>())); using L = boost::mp11::mp_rename; return try_cast_impl(L{}, obj, std::forward(unary)); } /// Like try_cast_over, but passing the types explicitly. template decltype(auto) try_cast(py::object obj, Unary&& unary) { return try_cast_over>(obj, std::forward(unary)); } scikit-hep-boost-histogram-5f6f3e3/include/bh_python/vector_string_caster.hpp000066400000000000000000000045431516627434300277140ustar00rootroot00000000000000// Copyright 2018-2019 Hans Dembinski and Henry Schreiner // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #pragma once #include #include #include #include namespace pybind11 { namespace detail { template <> struct type_caster> : list_caster, std::string> { using base_t = list_caster, std::string>; bool load(handle src, bool convert) { if(isinstance(src)) { auto arr = reinterpret_borrow(src); if(arr.dtype().kind() == 'S') return load_from_array_s(arr); if(arr.dtype().kind() == 'U') return load_from_array_u(arr); } return base_t::load(src, convert); } template static std::size_t strlen(const T* p, std::size_t nmax) { std::size_t n = 0; for(; n < nmax && p[n] != 0; ++n) ; return n; } bool load_from_array_s(const array& src) { const auto step = static_cast(src.itemsize()); const auto size = static_cast(src.size()); const auto* p = static_cast(src.data()); value.clear(); value.reserve(size); for(std::size_t i = 0; i < size; p += step, ++i) value.emplace_back(p, strlen(p, step)); return true; } bool load_from_array_u(const array& src) { const auto step = static_cast(src.itemsize()) / sizeof(std::uint32_t); const auto size = static_cast(src.size()); const auto* p = static_cast(src.data()); value.clear(); value.reserve(size); for(std::size_t i = 0; i < size; p += step, ++i) { // check that UTF-32 only contains ASCII, fail if not const auto n = strlen(p, step); std::string s; s.reserve(n); for(std::size_t j = 0; j < n; ++j) { if(p[j] >= 128) return false; s.push_back(static_cast(p[j])); } value.emplace_back(s); } return true; } }; } // namespace detail } // namespace pybind11 scikit-hep-boost-histogram-5f6f3e3/notebooks/000077500000000000000000000000001516627434300213325ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/notebooks/BoostHistogramHandsOn.ipynb000066400000000000000000015473351516627434300266360ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Using boost-histogram\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import functools\n", "import operator\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1: Basic 1D histogram\n", "\n", "Let's start with the basics. We will create a histogram using boost-histogram and fill it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.1: Data\n", "\n", "Let's make a 1d dataset to run on." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "data1 = np.random.normal(3.5, 2.5, size=1_000_000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's make a histogram" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "hist1 = bh.Histogram(bh.axis.Regular(40, -2, 10))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Regular(40, -2, 10), storage=Double()) # Sum: 981542.0 (1000000.0 with flow)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist1.fill(data1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see that the histogram has been filled. Let's explicitly check to see how many entries are in the histogram:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "981542.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist1.sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happened to the missing items? They are in the underflow and overflow bins:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000000.0" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist1.sum(flow=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like ROOT, we have overflow bins by default. We can turn them off, but they enable some powerful things like projections.\n", "\n", "Let's plot this (Hist should make this easier):" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 250, "width": 388 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.bar(hist1.axes[0].centers, hist1.values(), width=hist1.axes[0].widths);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: you can select the axes before or after calling `.centers`; this is very useful for ND histograms." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From now on, let's be lazy" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "plothist = lambda h: plt.bar(*h.axes.centers, h.values(), width=h.axes.widths[0]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Aside: here's step. The edges are quite ugly for us, just like it is for numpy. Or anyone." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 251, "width": 388 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.step(hist1.axes[0].edges[:-1], hist1.values(), where=\"post\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recent versions of matplotlib support `.stairs`, which was designed to work well with histograms:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 250, "width": 388 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.stairs(hist1.values(), hist1.axes[0].edges);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "No plotting is built in, but the data is easy to access." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2: Drop-in replacement for NumPy\n", "\n", "To start using this yourself, you don't even need to change your code. Let's try the numpy adapters." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "bins2, edges2 = bh.numpy.histogram(data1, bins=10)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "b2, e2 = np.histogram(data1, bins=10)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int64)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bins2 - b2" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0.00000000e+00, 8.88178420e-16, 8.88178420e-16, 0.00000000e+00,\n", " 0.00000000e+00, 0.00000000e+00, 1.77635684e-15, 3.55271368e-15,\n", " 0.00000000e+00, 0.00000000e+00, -1.77635684e-15])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "e2 - edges2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not bad! Let's start moving to the boost-histogram API, so we can use our little plotting function:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 248, "width": 388 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "hist2 = bh.numpy.histogram(data1, bins=\"auto\", histogram=bh.Histogram)\n", "plothist(hist2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can move over to boost-histogram one step at a time! Just to be complete, we can also go back to a NumPy tuple from a Histogram object:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ True, True, True, True, True, True, True, True, True,\n", " True])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b2p, e2p = bh.numpy.histogram(data1, bins=10, histogram=bh.Histogram).to_numpy()\n", "b2p == b2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3: More dimensions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The same API works for multiple dimensions." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "hist3 = bh.Histogram(bh.axis.Regular(150, -1.5, 1.5), bh.axis.Regular(100, -1, 1))" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def make_2D_data(*, mean=(0, 0), widths=(1, 1), size=1_000_000):\n", " cov = np.asarray(widths) * np.eye(2)\n", " return np.random.multivariate_normal(mean, cov, size=size).T" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "data3x = make_2D_data(mean=[-0.75, 0.5], widths=[0.2, 0.02])\n", "data3y = make_2D_data(mean=[0.75, 0.5], widths=[0.2, 0.02])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From here on out, I will be using `.reset()` before a `.fill()`, just to make sure each cell in the notebook can be rerun." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(\n", " Regular(150, -1.5, 1.5),\n", " Regular(100, -1, 1),\n", " storage=Double()) # Sum: 1905785.0 (2000000.0 with flow)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist3.reset()\n", "hist3.fill(*data3x)\n", "hist3.fill(*data3y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again, let's make plotting a little function:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "def plothist2d(h):\n", " return plt.pcolormesh(*h.axes.edges.T, h.values().T)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is transposed because `pcolormesh` [expects it](https://matplotlib.org/3.2.2/api/_as_gen/matplotlib.pyplot.pcolormesh.html#axes-pcolormesh-grid-orientation)." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 252, "width": 394 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist3);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try a 3D histogram" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(\n", " Regular(150, -5, 5),\n", " Regular(100, -5, 5),\n", " Regular(100, -5, 5),\n", " storage=Double()) # Sum: 1000000.0" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data3d = [np.random.normal(size=1_000_000) for _ in range(3)]\n", "\n", "hist3d = bh.Histogram(\n", " bh.axis.Regular(150, -5, 5),\n", " bh.axis.Regular(100, -5, 5),\n", " bh.axis.Regular(100, -5, 5),\n", ")\n", "\n", "hist3d.fill(*data3d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's project to the first two axes:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAuUAAAHwCAYAAAAByRFLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAABYlAAAWJQFJUiTwAABRCElEQVR4nO3de6yl2Vnf+Wft27lUnarq6mpfMMa3GNshihQ7Aw6OBmzPWJmgkJAZK/9gEk+cEYqlxohRkIgwJCMkRpMLNpOJE3AwwaAhiBAmisVlBjfEOEykHsgMY4ztZso2dne73dV1Obd9XfPHOQ29fs9z+l21fc5Z+5zz/Uio2LvWu971XvZby28/z3pSztkAAAAAtNNrPQAAAADgomNSDgAAADTGpBwAAABojEk5AAAA0BiTcgAAAKAxJuUAAABAY0zKAQAAgMaYlAMAAACNMSkHAAAAGmNSDgAAADTGpBwAAABojEk5AAAA0Nig9QBOQ0rp/zOzK2Z2s/FQAAAAcL693Mzu5pxfcT8bXYhJuZld6Vn/+iXbut56IAAAADi/duyeLWx+39tdlEn5zUu2df0b0n/RehwAAAA4x/7P/L/bPbt98363I6YcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgsZWdlKeU3pFSyof/967W4wEAAABOykpOylNKLzWzHzWz7dZjAQAAAE7ayk3KU0rJzH7CzJ42sw80Hg4AAABw4lZuUm5mD5vZW8zsnWa203gsAAAAwIlbqUl5Sul1ZvbDZva+nPNvtB4PAAAAcBoGrQfwrJTSwMx+ysw+Z2bft2Qfjx7xV69ddlwAAADASVuZSbmZvdfM/oyZ/fmc817rwQAAAACnZSUm5Smlr7eDt+P/MOf8H5btJ+f8hiP6f9TMXr9svwAAAMBJah5T/pywlU+Z2fc3Hg4AAABw6ppPys3sspl9rZm9zsz2n1MwKJvZDxy2+bHD736k1SABAACAk7IK4StjM/vgEX/3ejuIM/+Ymf2+mS0d2gIAAACsquaT8sOkzndFf5dS+kE7mJT/ZM75x09zXAAAAMBpWYXwFQAAAOBCY1IOAAAANLbSk/Kc8w/mnBOhKwAAADjPVnpSDgAAAFwETMoBAACAxpiUAwAAAI0xKQcAAAAaa75OOQAAJyrJ+6e8aDMOAHgevCkHAAAAGmNSDgAAADTGpBwAAABojEk5AAAA0BiJngBwUV2UBMjjOq6Lcr4ANMGbcgAAAKAxJuUAAABAY0zKAQAAgMaIKQeA46DxxpHzGoN8UWKtz+txAVgJvCkHAAAAGmNSDgAAADTGpBwAAABojJhyADgOqxZvXBPnvWpjVhc5Th/AhcObcgAAAKAxJuUAAABAY0zKAQAAgMaYlAMAAACNkegJAC11JWQum+xY009FkmTq9yt2nzvbnNhx1lj1hNGLUnwJwPPiTTkAAADQGJNyAAAAoDEm5QAAAEBjxJQDOJsqYqSjeGgX/1wTv3uSMclLxFanXpJvguOczzu2Mctz+SLYd028uPadF74f10b3XWOpcxOci/C+OKG47lWPZwewMnhTDgAAADTGpBwAAABojEk5AAAA0Bgx5QBO1pLrY3f1UxUjvUS/Ud9LrcO9ZD/Htm8Jmw77qYl3dv12r1tuFlwIvX4V3VQde802su+q6zAY+r4lNj28j5c4p1V9EHcOnHu8KQcAAAAaY1IOAAAANMakHAAAAGiMSTkAAADQGImeAE7WMSR1ht1q0l2w3TKJgvH+/b6iRFPXRpMikx9PV2JnlBBZlQzqEmOPp0hSeN4rija5XUmbxXQWDKf7HC+TPBsWSapI4qwpktS572hfOp6aBNLTTPwk8RQ4FbwpBwAAABpjUg4AAAA0xqQcAAAAaIyYcuAiOqYY0Sh2OIw5rtn/fe+8u1hPzXZVMdJRvxXHEMVJ+24G8rk7Xtx9d1JFbILxmEXx4uV3Veer5hx39WFmGu8fxrMfU/yzj/fv3ld0/bQwUWeM+bJq+qk5N8SPA6eCN+UAAABAY0zKAQAAgMaYlAMAAACNEVMOXETHFINcs1b4ScWj1sQtR+PTdb9r1tReVm99rfwiGI+LU5bjSsPgMZ3k2HPFeuzLxvrreKLTJX2HcdS6na6jPgo6roi1Ds+PyJOJdBPFr1esrV6x1nvNuul6X1bF07v9LJkXcly/x1VfWx04g3hTDgAAADTGpBwAAABojEk5AAAA0BiTcgAAAKAxEj0BHNAkrJoCQxUFfHKYC9qR2LZs8ZRlCtJUJKiF/WiCaEWCnw38IzfNZf+6L03qNLPUk4TWRXAMkvyZ1tZ8k8n0+fd9xP4dORdpUDFmTXYMzo2e0apiVdF1qLifXMJo1E+uScisKKQktMjUskW5dLs44ba7jd95dH8t8bwA8Lx4Uw4AAAA0xqQcAAAAaIxJOQAAANAYMeXAeVMTj71krOdS8ajBeHS7nsTz1vQbFo2R7cLiQS5me+j7qYlFrzr27iI/7jhGMh6JN4760XhtsyDOPIhTTutLvJcJC0ZpHkFF/LPGkEfnZmO9bLI/9h3pdeh35zmEx7BMTsDMXxuXU1HRb83979osUYjq8Ev5oqKoVITCQMCx4005AAAA0BiTcgAAAKAxJuUAAABAY8SUA2fJca3fPQjiqJdYdziKWdW+82Ti2ywRm+72FW0jbVI/OM6OsZgF8bpBvLPGLtfEdUdrcZtup+uWB7Hz7vwFcedJxxxdq37F2twaIz0N+tExRrH8cj/lcRkfHq6jrjHbUdz5umwXrKuu+4rWi3f9BtfT9RvcO3oO0zCI6+6Iua9aXz/aTu+LcD+yPnzVvpZbN524c+D+8KYcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGMkegJnnSZfRomLiyUSrpZNyqoYj6pp4xIVg4Q+l5wXnQtNPI2SB0ej8ouaYji6jZk/F1EhJelbkybDhDo59rQW7Hsm2wVFdZy1IDF2Oi33FV0rHc8wSiSWBMhLm+Vfj4OE4IokTpfcGyUoSxEim0x9GxEWnpJrHCXuRonNnfSc1iQ+Rz+Z4yocVpOguUzSOYmfwPPiTTkAAADQGJNyAAAAoDEm5QAAAEBjxJQDrVQU51mmn6gQT11RkW49jXeOYl9lX8mimOiO2NLo3Gjcck1xl6mPHQ4L+GgbjfFdJubdzJIWXYmujcY7a4x5UFTHFRTS+HEzX5RoEdxbUey30vFF++rat1lcgOk5wrj4fSkwNOqOVQ9zDXTIW5d9PxJnHpXUyXqfRkWb9JxG513vJzmGnIJt9Dijy6Dx9UHRH8tSDKoifj2OH6+4Dzp+13H+S9YvusdzkrHpxMHjFPGmHAAAAGiMSTkAAADQGJNyAAAAoDFiyoHjsMT6wCmIQV5mPXHtJ4oRdWth18RIV9A1tc3Mr+lds651X+Jwo5hkic3NsyCeV+KfUxTDHY25Y3zx+tgV11zbRP0oPV8a021mtrdffh4E51i3023MzHRtdYnhDkX7qolN13XI9f6PYps1/r/m/EV0eGGcd8X1nMl2wT3g1laviMHPY4mdD86n5kdE6+Lr7zxcaz147rg2mjsSxc67ugHRGvzPv02Y/+LaHFPuzbKIIccp4k05AAAA0BiTcgAAAKCxlZiUp5QeTCm9K6X0Cymlz6SU9lJKd1JKH0sp/c0U1k0GAAAAzodViSl/u5n9UzN73Mw+amafM7MXmtlfNbMfN7P/KqX09pw7FroFAAAAzqBVmZR/ysy+1cz+XX5OdYaU0veZ2X80s//aDiboP99meMB9qkgOCpOwXJGfmn6lWE+YQNr9v2drkrtcMlfUb0WBEJfAp+ciLHRTbhOlq7l9pWB8WownKqSkyaDRudA282DMmhRZUbjI9RNdz61LxecUva/QBMMgMVCTG/M1X1QnjeXYo0RBV/AlOu+y/5pzrImdNe9lojY1BWkkD9WNz8zSVO+v4FxIQmb4O9ciSHrfhtsE10/baOGuqDiPO8/B/S/nK0qODp8POp6KpFLXr+77mBLigbNgJcJCcs6/lnP+t1nKpeWcnzCzDxx+/OZTHxgAAABwClZiUt7h2dcO3f+zHAAAADiDViV8JZRSGpjZdxx+/KWK9o8e8VevPbZBAQAAAMdspSflZvbDZvanzOwjOedfbj0Y4EjLxDRWLCqUBhJ7Gu1H+wna9GoK5miBoZp40KCNi4sP4sNTR5GTqHiK62PLxz+7WNxBxX8M1BhzMx8LHsXzajxxFAcvxWXyWnAdpJ8s26SoSNK8jLvNo6DfoVyHYUXBqHkQj70hxxD0k6bleV8E49FrnrQQT3CtXJsaUUz5uIzzDgta9WVf0T3Yk+0mGohu7l5JUR6BngtpE0bO62+tIqY7inl38eHB8yLPpu471488m8JtOp5xqXdMseDRfmqeyRXPzlMdDy68lZ2Up5QeNrPvMbNPmtk7arbJOb/hiL4eNbPXH9/oAAAAgOOzkjHlKaV3m9n7zOwTZvbmnPOtxkMCAAAATszKTcpTSu8xs//ZzH7XDibkT7QdEQAAAHCyVmpSnlL6XjP7x2b2O3YwIf9S2xEBAAAAJ29lYspTSt9vZn/fzB41s7cRsoImjithKCL9hEV1dF8V27jiJNFYapJKNQEs2pcktoVJbJqoGPSTNjfKz3oMUbKe9BsVd3FtouRLtd5dlCUaj0uuDJIS84Ym6nYXv1msyTY9P74kBYYWwyBJUm+l4BboTSThMCi2NJfEzt7EJxjOL5fJskkLF5nZQpOWJb82SurUoki57xMXezvjso0WKTKzJNcqBcfg7vd+cMLmkti5HiQJB4m5jt67Y/ldRfvWpM2aZ0Eg75fnKypKpGc5LBQkY0zBfVpTYGgpJ5RYX7Xdksn2QI2VmJSnlP66HUzI52b2783s4eSzxm/mnD90ykMDAAAATtxKTMrN7BWHf/bN7D1HtPl1M/vQaQwGAAAAOE0rEVOec/7BnHPq+L9vbj1OAAAA4CSsyptyYDWcZCzgMrGJIi+CmOSksZ3dBUOigj7uu6joj8aQawy3mY/FjcascbfabxQLHsQKO5c2uttE4xFaICcsziNjzBoLbmZZYr2jmOgs16s3kX6DbRYakxzUedLw9fmav1YDCRNcVBRbmm3549QxWxDjPl8vz+lgt7wHpleCfqflQfT3g3j2LbnmUdGrdek7uAd6e1pgKPgdBcV4XBv9YjMoQnRvp/yshYqimPLdvfJzVNBqu+w3zPnQfVXEoadRcG9rAaQoxl1iyt1zKCq+5HYe5EtU1MEKn5XLqPk3gRhyHJOVeFMOAAAAXGRMygEAAIDGmJQDAAAAjRFTDjxXzTrly7ap2v3zx6xGcZJumyCOVOPFUxALXtPG7zyIu9X9R+uxKxcjHZwHPafRuZIY1rwRxN3KceWKOOoornt2rYxldnHVZi6we7bpr01ftpteLcecgmu+GJTjyYPuWPWon73Lsgb5LFiPXY59sOuPc/9G95j7k+c/F/1xsLa5xKZPrgXXU3Y13PFx51lOT9IvzGy2vl728/Su70fWnY/WVs+Dsp9o/Xpdp9/FdUdrnWsMefTbq8m7mMt9sbHumuRJGV+fp1PXRp8P0RrpLs5c7ouka9eb+d910G/uDoP3YwmeF+55ukS9CeA48aYcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGMkeqK9mqI6J7GfZfcVbKMJTTUJmSkq/qFJTa4wUDDeikTKmqTNcDzaRvcVFTmpSezU8URFTpQUMMlBolteL/uJEvEWUuRHC/wcfFl+nG/48aV52SjIHbSpJiYG9Uw0IXO6JfdSdIrHsu8gEXU+0s++o77k7419zp+N7pX72nuo+1rpMZn5RM+e7Hu2GRQcGklhpWnwu9JLnIJrVfEz78m9Mr2+6dtMyt9nb+4TIE0KT/X291yTLMWM0r40iH5D+mzYH/s2KkqYlmsTJWjq7zr113wbSQYN96X3pVa0igr8yHjC8amowJA8K2uKCYXJoDX7B44Jb8oBAACAxpiUAwAAAI0xKQcAAAAaI6Yc7a1SMYZoLEvEvLvY62hXU18gxMU0uhjHoF8XKxnETkq8eBoFRTvmclxRfKWMOQ2C8Wi8+HoQj6ptpHBR3gy20ZjVhb8O80vlcS7W/Pg0FlwL1JiZ9aZl3/O17vcX842gIJOcw0Vw2ju3CU7x5LLE/EY5DPLVZCuI85ZY5t48KDB0o9zX+jP+vE8ulW0WUY0fLeDj2gQxyTKcKHbexZlXFIAJY9OlqM5ize9L75V+NJ79MgZ5sRUU55H7v6ex1lF89qwvTaI28kyJnjFaYCiKTdfxBLLWMorabO+UbeQ5lC2K15bj7BzJEfHimo8T/I40Xrwm7hw4SbwpBwAAABpjUg4AAAA0xqQcAAAAaIyYclxsy6yRXhGzWrXrKO5c918T4yhrCvc2Nny3k0n5eRzE1FasFZ42pe8oprxCHsla3AOJIQ9idTXOdjH0gcsaLx6Zbei+g9hh+W6+HqzxLetu713352Ig57k3C2K2r5d9D/bKNuMHgrjl8nJaChZJX8jlnPllt23mwp2DfuTSzIJzMZfbYrjt97V/XdYcl3Bn/WxmPqY8XLO97Fevi5lZX2LIdf3z6LtobfPBXvnlRNehN7PBnsRJR2kq+ruW+723KxfYzGxQHnzuB/f/WE5QEFNuMxnfRrA4vW4X1TnY3S33HT0XN+Wmkxju6JmT3frnQa5GzdrhuXud8mhdct9NxfN+lfKicKbxphwAAABojEk5AAAA0BiTcgAAAKAxJuUAAABAYyR64nyoSb6sKQwU9FOTDKTbRYlIveGgs01Y1Oe5fx8kh7oCGJMgSSxK1OoSJX5qwZKogMlw2N0m+q7j77Mkui3W/bmYS7GgKDHQbTMMxjIqN5xt+Dbjq+XnqGDOWArQRAmZkyvlZ0081aTOgzbl56nP7bUkuXpR4aK83t3G9RPcFn0Z4zRIKtVkUHdcwWXo70mT4Cec5TZYBEmcCymSNAjq5Uxlu8G+TwycaUGaILE4y72ber6NFhjSbWZXffJlf7dMgEzjqWujCdTJgguhRbeiQkGa6Bm00cTOHD1f9dkpj7zwGSjPyUVQ3KjqmayF1rSalllVMqj7dyP6t6ZmwYBlFhXAhcObcgAAAKAxJuUAAABAY0zKAQAAgMaIKcfqi2L4NB6vJoYv7FpjpCv2VdNvVBhI2wyDn99c9qXji2LDNS4z2LcbT1T0R2PIu+K+j5DXy8DkNPPXJq+VbbIUT1msBUVFpM30cncbV6TFzCZbEncbxAVPLku8bHTapajOPKjBojHQuo2Zj9Heu/H8fZiZ9SSceHzDH8NivdywvxMUYdHbP9jX/Er55ei2v3d2H5TY3DXf0UC2G8jOo3h2/S6Kr9fvchQXPy33tXYn6EcKO02CXIP+ROLO94K480uyXRSPPddiRuW1GW4HRX+0eNYlfzP1doJgeddI7oNxsI2OOSrWs1YmUaSgTZ7KjSr9popcl5o8mroY86hzyRGIHttBHkiXsOAQMeSowJtyAAAAoDEm5QAAAEBjTMoBAACAxogpx+pbNhavIhbcrUub/dq/frtgbV2Newxj3CWOWtcCNr9Oues3Wtd3vYwt1fWDzcysL9/pWuJR3xtBkLS2GQWLc4s88o+ZxbA8rtllOe4gznshMb7zYD1qjSmfr/lzsZBTOr3m27h1ty+5JjaV9cX7QWiuxplPL/s2jqynPNsM1k7eKu+ddCd4lMstOLsSxAWrYE3tNC/P6eQlQWD3QmL5d4M15DckjvqSDLDv992/J+vOb/ld9yTOu78ftJFLvPeC7n7Wbvs2M1lrPbpPp5ckPnwnyFm4Wt7vg53y2iwGURxzeY379/yBZskL0c9mZknjusN4cYlX397xbaL1zXVf8vzKuuh9RMcX1W6Yd79PzJPgWd61TfR81WOI2iwT08665QjwphwAAABojEk5AAAA0BiTcgAAAKAxJuUAAABAYyR6YvXVFPSp2K6mCEW0L92uKqmnHyRASlJpWDyoInmqq18bBedLE75qkrsCebPMdIuSOPOwonCSFBTqyefZJd+vJnZqsRczs+l6eezzIEdseqm7aM1sUz4HiZ5aUChK4pxtlcelRWzMzGbXJGlzIh0PontC7qUbQZapJF/a1N8Xa1fK7WaT4LzLeHoD/9vT76bZ3//JbSfjG/vx6bnp3w2ShtckgXTDn6/BtvQd/IRHkhc4D37CPclT1CJToeDyje6VX04vl7+ZYZBXab3y/C2ub7om/Z3yINIieE7q7yZMpJTt5HdvZmb7cs9FxYP0u+iZp9tovzWiAk0VCZq+n+jfle7nmVswILJMYifJoBcOb8oBAACAxpiUAwAAAI0xKQcAAAAaI6Yc7UUx48+1TOxd1E1FTGEUL67fRfGDLn4xKgwk8ZRhjLvGgErVk7CQRpIx18SlazUVM7OBHEMQ/6kx5Hk9KE4i8eFa0MfMbHq1jF+fr2vspB9e7nUXD/KDCb7SUxzUM5nLpYlqnswkhjwHxW8W67KzK/5eTnvlzkYP7RWfJ3v+ml+6UhaO6fd8v4N++d0gaHNnt6xudOmSL0hz5Ub53d09X1Tqocvbxeedqz4/4fZOGZc8GZf3zpUHyz7MzKaz8tzs9HwcddrujvmdSwGmxdCfi8WwvAcHm/7mWbtVfh4EhYqSpqkEjy9XCGut/DzY8xvN18rj7O/759livWzTC+o8Odmfv6pSOAP9kQTPTikulqUIUfQMdIXNolh1eU6moEhSHktsevDvSBoEz9MO0b8R+m9CTZugY/8dMeQXDm/KAQAAgMaYlAMAAACNMSkHAAAAGiOmHO1p3JzG1tXE2kXxghrnXRELHvVTtQatxBCm3B2V6eLHo/H0y2MPY9WvSHDzOAgk1RjyKDZ9VsZupig2fSDjCdos1srHyvRyxdrEGrcfnD6Nu43a6BrkUTzvXEKio/XFNaZ9dtkf5+ySrLUerLOtwbm9URAHLLHoVy+VMeXpcvnZzGzYL/uJYsoXcoJmGihvZn/yhU8Wn68MfZC09rN2zd+DT4/LhdwvD/09uD4o19C+NynjzntBIsForTzOneTXy157SRmnvL/jFxjPErcfrdm+GJX7z/vBmvIS0h7dO6M70u8wehaU321+Sdbp3/TjG90tz3se+H4XmrcS3P8u5yOqK6C/6yhe3J3DKHheYr87nm9mZia3V/iclOdZnkxdG1+nwjdxKmK4q/49WAbx4zDelAMAAADNMSkHAAAAGmNSDgAAADTGpBwAAABojERPrJ6uxM/ou4oEzaigQ01yTU3xIJcIVVHMKCryowWONKEprfuiLC6xM0rQ1ISqoDCQ6XEFSVhZEz2DIkRa5CQsniJJarON+y8MpEmdZmbjq+XnflDcZSa5gtMgiXOwW/Y9fcAnaCZJ7MwvGLs26xtBAprY2igHORqU+7qxUSYympndlSTJl2/dcm2GcuI3+j758sFh2fdjuw+5Nl+zUfY9zj5JeLoor3m0r1de/nLx+f9+5quKzy/Y8MWDPnfvgeLz5jWf9DqZyL0cFZ4alF/2/KVybSbXghs3S4GhXd9Ekz/Xbvs2ScY42Srvt/4kKD6TyuOMCgz1J92Fuxbr5fXr7fhrlSUhs6eFeMz8szN6vk4kazNK7FTyLNVnoJmZabGgqDCcPpNnwb8Rul3Nc9u6i9CF/0Z0/btGoieMN+UAAABAc0zKAQAAgMaYlAMAAACNEVOO1RfF2lUUGKqJBXcFhsLYxIrY9LkUyQhitl0RjCD2Ow0lXlfjIrX4hpnZTOI2o7hz3VdQhEj7juJRtQhRVGBIi5pERX6yFFRJWjAkOISa4kE9CeGebgX9SPGg6NXEdEuu+SCIR5XPURT8lc0yXnw889fv+mYZmDxblAPaHPiY35dferr4PM2+39dsPlHue+Fjwa8PyjjuqJ8tCczf7N12bQap/N3oNmZmu/OyqM8bH7xZfP6iJgSYj6ffn/nf1aBfXpvp0P+G97bLG2qx6ZpYkiJOwyd8EaKFfDVe785HcPeb+VyHhfzWBkHBnPm6/PaiXI1R2WZ0x+c0aPGgSF6X87wfFCTbkAPbDgLstY0+d/TZFQkKF+l2aRQUjJp253OEz/IOOfonQv+tif4dcR11504t1S/ONN6UAwAAAI0xKQcAAAAaY1IOAAAANEZMOS6OcB1YWVM7iNmuWqdc166N1uPVuPORj/F1NOYxWoPcrdkbHOdAjiuI08wjaROtDyz7z1E/8tViLepHPmvo/MQf52xTrkPw9JJlpMN4Xt1XL1gTenJDYlb7fjxrl8vA4LWRj4/V+PDXv/APXZtP3ynXBv+T154sPr/xymNum89NHiw+3xjcc23WJcD+5aMvuza352Vw9YtHt12bl4+eKj5/cfqAa6PbvXbti67NJ/a/uvj8f939muLzn7vmj/OjT7+2+Hxj06/ZPpY482f2NlybjQfLc7Gva5ub2d5OGXc+u+R/R7q+uM2DmGT5au7Dnd393x+XX8zWfb+aL6H3etTPYuAb9cb6rAr2tVOuS77Y9AeR9jWuO3ieaQ0Fty6433daK/eVtY9IFGutz8ogZnuZ2O8oDt39m1BVp6Iitp8Y8guHN+UAAABAY0zKAQAAgMaYlAMAAACNMSkHAAAAGiPRE6tv6aSZ7iTOGppskwZBQpMmdgbJllFBoa5+kh77WlBVZyHJQFEiqiZorvtjyKNyfGniExfzsEzC0mIlZkFiZzCcxfD5i3bsX+9ODl0El1OTP4NaOJYHkgw3DAbYk+JBLsPPbOtSmeg57Plr/tBmWZznsbs3XJs/fb1Minzp+q3i85OzK26bV6yVyZfD5BPCdqXSzTyotvTqUVlgaBpkz17vl0Vh/rO1Z1yb39ovE0+fmF1zbbb6e8Xnb7j6B8Xna31ffOarN8t9fWnsq0HdvHu9+PzKB265Nruz8n5/fO7PaZ7KPXfZn9OFJHYOn/E3mCZ2aoKmmVlP8x8lsbO/HxTl0l0FSZKLvhY68/t224z8Ne/NpZ+pPxepJgFTi6Fp8mWQyKiJnWEhtj2pvhQl6GsCfGTcnWyp/45EyZc1RYiWSgY9poRRnB28KQcAAAAaY1IOAAAANMakHAAAAGiMmHKsvoqYuWXjxV3fUQxfVVyfxAtGMYYaAxoVApJY9LyQAh17ZVyumfm4zYjGogeFgdJc486Dqicy5iDU2hUPiqItXYEheRL1JWTUzGx6WfoILstEQoWDUGtbyKlYbAYxohKbO1z38fU7e2VHD1z2MdHX18rvXrjhi/zszcvrtzsv+33dhi/Eo/Hhrx496dqMpKDVTvb3yaUkRXWCBIAHJVb+V3df6tq8aHC7+PzG9addm09OyqI++zKeqfnf8KYEX79y0xdA2uiXxzCe+3/Wbo+jKlKlaw+V8f/PPOnj1/uXyn3NgsJTg23JZfG3jk2uSb9PSYPgRzNbK78c7Plrpb/HHDyHZpfL897fCwYov63F0D8L+uNgOyXPL5vJ5+AZ6IoHTYOgfI0X137NLC8WnW38NsF4KuLFa/oJGt13vzj/eFMOAAAANMakHAAAAGiMSTkAAADQGDHlaK8rZjuKvZNtorVja2LBNV5wmfjBgw2ln5oY92CdYV1bN+uxa5xkZN2vZZ4HMp4gljNLnPli2H0Msw3fZnSnjN0cP+Bjmefrcv3kVMzK8OODNrKrRRBK35Ow0elVf76yruXc9+eiNyzvp411vyazXr7r6z6mfCaB7y8c3nVtbk0vFZ91DfL94EB1ze9FsAb5eq88hqt6csxsLNtd7/s2vzMp1yB/y8bnXZsn5+XF0fjxg/GUfT81LWO2b80lacDMbgzLOO95EGz9ub1ynfLtmb//v7xd9n15feza7E7K8zy87K/5dLuMd+4tgrXC18t7br7hn0N6G0gagQ2C1JGBrF0+HwXPj0W5r37w7OztSp5K8EhxNQv2K+LHI7p/jQWP8momEkMexWdXxIe7/JegxkMel/dB9PyvWV88z8oxR8//zn6IMYfxphwAAABojkk5AAAA0NhKTcpTSl+dUvoXKaUvppTGKaWbKaUfSSk90HpsAAAAwElZmZjylNKrzOzjZvYCM/tFM/ukmX29mX2Xmf2FlNKbcs5+8VsAAADgjFuZSbmZ/S92MCF/OOf8o89+mVL6R2b23Wb2Q2b2nY3GhpPUleBSU7wnaFOVtKkJo1HxiJqkTU1YihIpJUkzjXxBDk1YTSNJ8tNEqcgsKIYjWYl5EGRJDiRJbNcnui00ASy4dPM1Oad9fx16U0lak8IoQU6iL2gSXBYdT5pGyXDlZ923mdlgVJ7D8cSfr+GgbDPo+ZOhCZi9oNrS9eFO8flSr0w+6wUn+VqvTCrVQjxmZnflHvzTA/8beXxeZhS+oOcTNL9x7U7x+T9NfELmVq+s9hQVAlpIEuLv7b+k+Hxj4AsrrfWCwjHi+qg8f1Gi51ddLY/h8btXXJterzxfg4E/73O5L/JekNAnX0W/kZmcwmE5PJts+Xty7XY5vtG273gmSaXh6ZNngf5ezcwGu9J38AjOG+XzK+3554V7XulzeuwTbjX50vQZaGamz+QgAV6f2zlKDnX9REV/gkUEuvZVUYQoXJygc0fRhSBB9DxZifCVlNIrzextZnbTzP6J/PUPmNmOmb0jpXTJAAAAgHNmJSblZvaWwz9/JcsacDnne2b2m2a2aWZvPO2BAQAAACdtVcJXXnP456eO+PtP28Gb9K81s//jqE5SSo8e8VevXX5oAAAAwMlalUn51cM/7xzx989+f+3kh4Jzo6LAUNL4z6h4hMZ5V8SYh/sKile4Nho/GRXXcDuTNtExSPGgtO+DTRdr5b61gIiZmUl8eG8axLVulvsKi/xMyjFPJRY2BeGWGkOeg+Fp/G4Umz6X8fTXuwuRpCAW3MUpb/s45dddf7L4vNXfd22iWOrnuqUByGa2PiyvXxRT/lC/jLX+wtxfqx0JgL6W/X1xJYgzV0M58a8dBvHh8nt8qP/bxeen5j468ZPjFxefpxqwHbgy9JV3bo/Xi8/R9ZzOyr5ns6BIzFy+u+TvnXSvvDGjuO6sXcvnnr9N3L29+5A/F5cfl8JA0yC3ZVD+hgfb0QDv/7njCpSZWdqT87MrBbaiAmqb5f2Wx0GsesdYzHwOj9UUmAvis48lFjzabpniQcSPn3urMinv8uyv4nmfFDnnN4QbH7xBf/1xDwoAAAA4DqsSU/7sK6erR/z9FWkHAAAAnBurMin//cM/v/aIv3/14Z9HxZwDAAAAZ9aqTMo/evjn21IqA61SSltm9iYz2zOz3zrtgQEAAAAnbSViynPOj6WUfsUOVlh5t5n96HP++u+Z2SUz+2c5551oe5xxyyS81JB+wmJCbl9BslJFok9vVCaS5YlPnsqTMmEpDYMMSLdzGXMvKh4hqRZBkYw0LH/qeeSPszcpt8tBsZnZpTJZdb4eFU8px5yiZEsp2KN5d+MgkC0sKKT71lspyAvMa+U1zzv+Okwkya+/5q/59qQ8F6OBH+BMBvTZvQddm6mcw+uD7eJzlMR5RTIBb05vuDYvGtwt95P9MXzdqDyGx6a7rs3n5+V3Lxv4fq71yvvrVlA85f+dbBWf9bienPmL/rlxeb6ekj7MzDb65e9qe7ru2rxqqywGrUWdzMy+8My14vNw6I9zek8K5uwH93/Fqy5N/uxJLuPcH4Lpv36DfX+OF/LbG0yCIjaz7iTO3JeDiBIyJ+U9mKZRcR7pZyM4MLUribrah5kv8hMVVZt0J4hWJW1W/HsUFQtyTurfOZwrKzEpP/S3zezjZvb+lNJbzez3zOwbzOzNdhC28ncbjg0AAAA4MasSvmI558fM7M+a2YfsYDL+PWb2KjN7v5n9uZzz00dvDQAAAJxdq/Sm3HLOnzezd7YeBwAAAHCaVmpSjnPoOOLoarbR/VTSQkBVsYGBrHHcYRGKip+bFuTQwkVRUQ+NTe8FgdQS75lmQYGhzfIcLoLiQUnOT3/sYzIHuzpGHxM9DmLRn2v9lj/O2WZ3rLpWMujv+eOcr2mlluCcSsx7f+Cv5739Mh77xVfuujaXJN7587vX/L7EnVlZPOXl6192bT43vV58funQ/4fEvpVjvjnz8exPzMvxvag/dm2+MC9jvedB0PRLBreLz+vB7/HWvCyC1JNqODsLX1zrxrCMr4+KB312V+LO93wRot1ZeQ/eHfvY5rVheUPt7fv7Ng3lPpj64xzck99R8LMfSui+HtbAh/a74kGjbX9P9ublvbwY+vtff5+LKL9Eiv5Ez50k+8prwfNiT4PlKwr66DM4eibLdssW9HGF4ILndl5UFKGrKTC05L9RnX0Qm36urEz4CgAAAHBRMSkHAAAAGmNSDgAAADRGTDlOVk2820nFxFXEs2sMebiWeVe/Zn4d32Dd3LzQeNRgPfFRGceqa5nnfR/zm9YkFjda19dtFK3ZLuciiiMdl2OeXRq5Nnkk8ZXRcKRrjY+drfvxaWzuaNs1sfmGfBEcpq6JHq8rXTaaT4P1qCUG+bNPX3dtnrhXrqv9n3/VH7g2e4vyGj+xfyUaUGF3VJ73aI3vhwb3is898/f/1rBcE/puENd9rVcGON9b+HjsJ+blmF8qMeZmZpu98t7V+PDP7L7QbfPQqDwGjR83M3vp5jPFZ41VNzP7/Pa14vOo72N+n/6yrIG+G/zzqPkHwaMrzcubLlpff75ZftYY8mh9fV2XXNckNzPrTfU37PtZSP2B3izIf9FclqCN2yZq4+LDpU1QU8E9v6J/HyQ/JwX1G/JUFoOviOkOc4q03oXGoVsQQ16xL5fPdExrpuNs4005AAAA0BiTcgAAAKAxJuUAAABAY0zKAQAAgMZI9MTZVJPEWVVPQhv5JJ6aAhOumyBh1PWz7pPqshb50cTPIIHUFeCICgxpQtUoSNCU8aWJT8JarPmCKq4fOfYo2WywJ4mdUrhothGcPznMqCiLJnYGOX++cFKU2yWPxt7GvmszmZXna2Nt6tqsD8pzGCVxrvfL7QYy6H5wAr80Kft59caTrk2Np2ZlP5qMaeYTMm/PfXGerX6ZMPrx3Ve5Nn05rs+ObxSfX7p+y23z+KRMYJ0FWbm/+eQris83NnZcm2d2yszKvR1//yc5z9HPqLctv+G+bzSQ3c/9rlybYZnP6ooARaJkUC0WNLodJJNLAR9N/DQz6+mzIErinOkPMkr0lDb6rIoSNMdScCi6ELpNkADvEkaDBPg8KX97UaK/Fg+KVP0bkbqLEAG8KQcAAAAaY1IOAAAANMakHAAAAGiMmHKsviUKMZjVxeylQUWMdE0/GlIYFQbSePCgTde+0oYv3OLiNDUm08xsU6rqROPTuPOoZod8XqwFMfhjKbYRFOSYXy3PxWCvbNMf+232r0vceXAqJLTZxaGb+Zj32aZvo4c+3fP3SerLcfpunD+44wsM3dgsA4x3pmUQ8tbQx7PPFuV5f3xyrXPf4yAI/yVrZeGd3UUQAC36QRD+J/ZeUnweBCf+ugRSPz0tY9M/t+fPzR/u+KJIqiex4F+457fZ3yuPa77vz4XmGvR3ghhk+SpNunMfBsHPUQsKZR1OUFNH8yMG+/4HOtgtd54Hfnz620q7foCueFCQX+JiyKPCO0rzaPb8vd25H/PPyTQMirXpczF4tmoMeU3xoKhNVT+aeEQhIAR4Uw4AAAA0xqQcAAAAaIxJOQAAANAYMeVYPRVrkNes+arx4lEbF5sYxKYvI4xxlJjtcM1xt5GsnRzEYKY1iQNOQXSzbheske4CqYdBvLisV9zf82tzL0bldotgPKN75XkfXyvPha63fLCNnIug374sVzwJQpIXEh4+2PH9TPXQ7wbXc1Sei/2Zf8eha19PXcdmo355Libzss3T48tum0Uux3xv5q/nJQlmni78vu9KYP4wWNh90CvH9zVrfj3x8bw8P3cXPuBfY9p3ZMzbwTHouXn8nl/nfWNY3oPPPOPXUc8TWXd7GBzn0+XvKFgS3dZupc42un54P4gpH8jPUeO8R9tB3HJNzLboTfxxpok888L1xXWj4Jmi3y2CuHPNXampqaBrhUcnWfNfasYXcflKJ7h2uP47VvPvHC4c3pQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxET6yemoSXijZ1RX+06k93v2EyqG5XkS8UJSomScBMmtAaja8n/9s6Om4d8yxIjNWk0n538lSaRwlp5efhPZ/pNr6+Jm3K8Uwv+3M8X5PxBMObSY2kFOSeDXbLzznoZ3i3/HLq8wtdESILEinHt7bKJjf8udgZlwmGw0F5Lj579wG3jSaDauKnmdmLt+4Wn/emvgDSgxvlydid+TYDuaC/e/vFro3uf3PgE4C1yM8fSpGfHBzD7n55bjbX/fm7vVNe9Hw3KAgmt2maB/fXetlosB28s5Ihju517soldZr5Yllyaqw3C35X+luryGPsTf3zorcn2dDR86wmIXN/3N1G6TMmelZpcmiQNK9J8ppEH6ooQlezXeoFxYxqknCXSOzUf2uq9kPC6JnGm3IAAACgMSblAAAAQGNMygEAAIDGiClHe12xfhXFg8I2XcUajtqua18Votg/H7/u2+SdvfKLjbIIS1SUyBXSiIoSTX2Mr++n/BgWQJI48yguXmt9pCBsdLArMeRb5b6imFot+hPF86aFxnn7Nnq65iPfZi61b0bP+OOcb5TXU2OSzcysL9/t+nO63S93Nlgrz03WYzKza5clMD54v3Lz6evF56ubPrj55p0yXr3nIqJ9/PrmyN9Ls3m5/8/v+jj46zLme7tSuGjgb5SZFGR6+okguF/0ZkGuhpzD/l7QRu4LLUR11HdKY8g1ftzMbO1ueWOOr0juSPDI6cn4evv+5u7vlo16k6Bg2qi8B9PEx2NrkTBbonCRmcX5LV396nNxXvGMjgrDSd/h80zi15fKQzoYQOd2y6gZD84X3pQDAAAAjTEpBwAAABpjUg4AAAA0Rkw5Vs8xrVPu1pcNYgE1Zi+OF3z+fqvHo+taR12PJHBaxufWxo76iOI0Zf3zcE3hNQmungRx6NKmNw7aSNx5FLO6uCJrc29LbGewRnp/XJ73xchfhyxrtmscrplZT8I03frn5uPONZ7dzCzL0zOKr19slOe5Nw7aSIzveF9ifoNL/tRe2Wb9sl+/ezIu29ya+/O1kFjr6T0fYL/xQBkkvT/xJ2O8U27XH/rfw5NfLuPBs4xnEoUt75e/x/6uPwbNWcjBtRrsdOcaaLz4YM+3UT1/2t14+sFPRO+5jVvyHApyKgZ70iaqESDrkke/o96kbOPixyN7wWLremPquuWBPJETpjUWzNy66TnKh9HnV/BMTprCo/sOtkvB8/XE1gZfYt1ynH+8KQcAAAAaY1IOAAAANMakHAAAAGiMSTkAAADQGImeaO84Elwqki9zlNAk2+WZTyrS5M9cUc8hTBjVhKHguDWlKGkhoKiQhiRc5Z5vkzRZKiowpIUqgmNIM2kz9slTPTnPi60112b4TJkUlkflvhbDIIlzszzO+dAnZW08XY5vPvJtxlfKfa3dCRLmpADNbNM1cQZaz8fM5nvlcUwvB/valvMsBYeiYjia5Lq/HVzPTTkXQcLofCqJblN/3veflIOP8t4G5Zfze8F4tGu5TXvj7ndEvak/CE3InG36AfY0iTO4Vi4pODhffcl37I+DfenPKCjyo8WxtJhWb+wfMvMNSQDWwlRm1pNz0dvxyZcuQVR/02b+ORMVsalIis/6jNMCPtHzbHH//x6cZJEdX/RtyYJzuh2JnQjwphwAAABojEk5AAAA0BiTcgAAAKAxYspxYYTFgzTOO4oF9Bt19h3FOLrCFNG+tCCHFMmICmmkkRR8mfqKOZ2x6ma++EfURgsMRccpMaFRXKse58LKfqNT0xuX/W7c9cc5uaoVfXw/68+U/Uw3ffBwkks8uus7ml7WWH6/L/1u/amgUJH0o3HLOQjdnW2Un/tBXZTxddnXLV9VZ6CFlNaDOOVJ6mzTvydFm4LYbz0XWqwnulYLud303JiZ9SWO2hUKsqCgT3BLDvYlznuvu2iM3idRPxo/buaL/Cy04NbA30yjL5WB8Hnd/z57UqgrTaJ4cflOK2WZ+d9+VMFKnkVhXLc+8+TZVBULHsWdL1FgrqZ4XFwoqCaJaIn3mxQPQoA35QAAAEBjTMoBAACAxpiUAwAAAI0RU472jiO2rmKbMH6xYt95IWs5V8Smu/hxM7+ubxS/KPGdWWM7c/c2Yfyn7iuKI9Xton407jxqM9Y10f065XocvT3pt7/uNunvl9cvdYf8xmtNy9rSw11/zReDcsPpJf/+IkmceRT73Zd47FlwKtaeKT/P9NCD4xze89/5NnLfRre/3jrBmujDnfLz5KrvZ3RbcgSCf1lc3Lsu2x+cv+F2+Xnhw+JtIHHmo3v+hM02JG4/WF9c48yjOP0kv6P+pPsmHOz431pv//njlHv6GzKzJL+ZtOsHmCbB77pLFLM9k5MRPXckdyTpGuQB/5wM6hFo3kz0LK0J8x6UN0uY5+NygYJzWpEv5DdaIjepZm3zmjY403hTDgAAADTGpBwAAABojEk5AAAA0BiTcgAAAKAxEj3R3kklqpxUcYYwGVQToYKstYUU9ggSRt2YNSEzKOijiUdhYSAtQhQleupQFsH5GkqmnRYTMnPFScICJlHy1nO3mfpt+nM5hqF/p7D+VJmgNt8Mzpckp7qCQ2Y22JVjCE6FSwYNihBl+WotSDDsSV6bJjxOrkSJu9JHcDn1u8GebzO9VH7WxEozn1Db3/Xj0X1pcSMzs1FX0mZwjt2+b/s2WpwnKuI02JXk3h1/HRYjKeAzj5JBF51telNJYp74A+vJ/Z0HmpQbFBzS5M+gSJh7XvSiylPSJkrilN9I3tn1bfpyoqPEdU2KjPalKtrERX6kjRYGChI086yiwFBNgTn9N+GEFisgqfP84005AAAA0BiTcgAAAKAxJuUAAABAY8SU4/xaJv6uouhDTTxjXTGjaPfPH2sdFvqQbaJ48TB+XduMJMg3Ok4t7BEV0tCY9iDs3BUYWi8bRTHlSSqG5EUQ/ynFSPq7/lzkfnm+NoM2063yXITxxfKdxi2bmWWJO5+P/P012ZJrLt0MgnBeLWIzH/n7Zv2WFGqJYrYXqbNNTwrkTC/7ffW1XlQQv6403l6L95iZrd0pBzS+6s/fcFuOMwqRlmulcehmZr17er6CNhONKY/ixSueOxLL3NuXojVRUa6Z/CYq8kJsL7gQmheiv+lITUGyKGa7q9BO9Dxznfg2+pwMn8myXfxs7S4MtFTxIGBJvCkHAAAAGmNSDgAAADTGpBwAAABojJhyXGw1cecSZx7GZ0s/NXHnKezm+beLtnGCPnKqWI93X4J6e/5/syddlzxo4+zt++80fn1SxrWmaA3moTyuZkE8r67HPvTHOd9aL/elsbpmNtgpP2ddk9nMFrJOegrWV57LotmDYF9pXrbRdbZ7QQirhtPP1/34NBZ8tuFjagd7Eps+7F6DfO1OTU6F/2qw//zbReuL92XN782ngvsida8vPtgpT2J0vgaSW6Ax7wf9lPfpYs3/E5r2y34WG7ogu79P9XeUdoLfTA393QT3W9Va4RXx4q72QVTXQNpoDYUcjUXXFw+eZy6mfNk4b33+BzlFVTlEwDHhTTkAAADQGJNyAAAAoDEm5QAAAEBjTMoBAACAxkj0BLpoEmeQYFhVnKerMFC0TUXhiiQFMCxISlQuqdPMkiZSRttpwZKoUJEmS13e9B1tSyalFhwaBImo61LQZ3+5oie9PSnUEtZj0uIkQdc1hU8q9Pfl/pLiRou1IOFWboMUJNn1tGDO3J+LqFiQ35ckugW3sSZFRgV8utpoYqqZWX9fEjSjcyGb6TYHO9fiQf7A+7tyPwVtFpvlPTi4tePaaHJx/44v4OOulyYTRoWB9DcS0edD8MyJiostwyV2Rkmb+jsal8+dZZMo9TkYPVu177BNTX7oMkXogCXxphwAAABojEk5AAAA0BiTcgAAAKAxYsqB5wqKR9TEFLrYyGCbvNAiRN3DqSmKURNfqTGrYfy4xsVH+9YY7ajN+lo5vnvbvpuuGPzxxH2VdN9RH1KUKE26486joj9hUROxGFXE4A/kmgeFbdL0+e+vxTSIr9fiKX1/zReD8rvhXpALIcc5HwVFiGbd56I3KfuebfoxDyR2XmPy8zAo3CLHMLrtcyEWo3JfveB8JokPTxMfV+2vlb+3+/eksE2Qs5AkZ0FzIczMTIsDud9VcE9MO7Yx8wV9Ku5/qyj6s3RxHo1f19/soiK+/ZhiusP4dX3eEz+OxnhTDgAAADTGpBwAAABojEk5AAAA0Bgx5bjYTiqmMIpNV0vEqveCWHAXKxnFTupa61EcafSdcHG2vSAOWONYg9jXbBIHL2POwblxsd+jkR+gHvvUx9SmYMyujcQca7yxmVl/u4zxzUGMee/pcv9RP1njbKVJCtYX74271w7X855mPi54rutu7/o2C4n1Huz6OOD5WnkM61/ya3MvZP1uXas77fj7VuPFozXl3friUdy+3ttRLPh+eVxRroHtynFFa4dLDYAU1ARwuRg6vugeld9V9Btx93/wm66KD6/IqQjzSbQbzWXRReXDAgAVdRZq1jeveZbXPKeBU8QdCQAAADTGpBwAAABorPmkPKX06pTS96aUfi2l9PmU0iSl9GRK6RdTSm9uPT4AAADgpK1CTPn/YGZ/zcw+YWYfMbNbZvYaM/tWM/vWlNJ35Zzf33B8AAAAwIlahUn5L5nZ/5hz/u3nfplS+iYz+1Uz+59SSj+Xc368yehwvi2TDLRkMqgW9YmSlbSNFtlZTH2Snes3yr+SMUd5gb6wR1BUJypq4vqRIixR0prsK8uuw+JCmnwWJZpNpOhQlIi3s1t+3roU9FMm1SVfy8gXZNrb92021svPYYKhJAZqAuRmlNAqfUTFcNbLY09Tf76i71wbKbyTg+vZ29Ft/Hh6Vh5HGncnBPdk3717u66N2y4qqtO1TSS6vzRZNixII31Hxby0wJFc86wJpWZxsSylY44SNuU3nPQeNbO8W57n8FlVMx5J6K5LMpVzERYxO6YEfYoFYcU0D1/JOX9IJ+SH3/+6mT1iZiMz+8bTHhcAAABwWppPyjs8+yqlohYvAAAAcDatQvhKKKX0MjN7q5ntmtlvVG7z6BF/9drjGhcAAABw3FZyUp5SWjOznzazNTP7OznnZxoPCRfZMnGHwTZhrLfbTOOx77/AUKoI9QzjNCsKcmj8ehTnqjHkYaEi2b/Gp+ZBRXGQKL5e+5n5OO90WWLI7277vrVIU1QARmOH19d8Gxeb7osZufj6Qfm5dzeIL9bxRedC4p/zWlDcaFeC5YMCQ25fKbie+t9co7jzOxKnLP2mRbDvikJPGiMd0t9jVDRG4+CDwlMuRjvKWdB+Fv43ksdy3vV3FR13TeEu2XeKxqc5KOPo3pbnUJTi4eLXjyc+28WvR9eq5np2bQOsoGOZlKeUbprZy+5jk5/OOX/7EX31zeynzOxNZvazZvYPajvNOb/hiD4fNbPX38f4AAAAgFNzXG/KHzOzYOmBI30x+vJwQv5hM3u7mf0rM/v2nGvq/QIAAABn17FMynPOb/1K+0gpDczsZ+xgQv4zZvYdOdf8B38AAADgbFuJmPKU0sgO3oz/ZTP7l2b2zpwJAMMZUnG7unjnKK5b1+jVGPNw/e7udX2r1kivWitc11cO1jJfVCyWJGuZu/js4D+QZYnxTSO/frc79mCN6LxdLqodHqfGwUdtdF9RPLbGO0fx9RIbnLTf0bC732BdcJVyEPOusfLBmtVuXesgfj1ci9sNQNbgv323/HuNXTezpMep942Zvwejc+zWMg/aaAx59B9p5bu8H+QsDMvrFcZsu9+oPBt0vX0L4sOj54d8F/1n5hxdv2W4Z1V33QX3+zzJWHCmEDiDmi+JeJjU+Qt2MCH/oDEhBwAAwAWzCm/KP2Bmf9HMvmxmXzCz9yZfbe2RnPMjpzwuAAAA4FSswqT8FYd/3jCz9z5Pu0dOfigAAADA6Ws+Kc85f3PrMQAAAAAtNZ+UAytPk5GWTHkIEzs7N9IkzqV2HSZhqc6krGA8YaEiPV9REqAmzGkxobBb6bfmfEaHoEmRQYGVJAmPYbKebhMlg2qxoChRUel4ogTSqSQYRkVi1G5QhEjPRZTcqGOO7iVNroyK32gbl/RXkSy6F6y8W7NdTYEhvf+DglFJC0QF/eZpcJ61jZ73invZbRO1cUmv3QW24kJnFb8tLTBUUeis6lmqbWqKBwHnRPNETwAAAOCiY1IOAAAANMakHAAAAGiMmHKgy3HFLx5TbLqzTOGiqOjPEnGkUbynjw/3sdaujY4vim3WMVfEZ7s4dAuKsASyxi5H8eIagxzFbOsYZ/5aJXdfTF0bNz49fzXnaxAlAAgtoGNmtl9RnEoLHNXEeSeN4Q4K8ci5CUtYaDGcoAiRaxMWxqooYCVx5jVx3jX3m4vtr4mjrii8k6JzIfdFmG9S8aw6jhyZ4zpOYsxxXvCmHAAAAGiMSTkAAADQGJNyAAAAoDEm5QAAAEBjJHoCp+WkkpGWScqKEjRrCgxJftxiMgnaSNJmTWEUbRMlRI5G0iZIsnMJrcE514IvQcKoK7BSkdAXJTdmSZxMw6FvowVeNDmv4rrkeZCgKePR4lBxP0ESpys2ExznWO6D6JxKwqNLktRkTDPLmqgbJfdKG5ekG+w7LNYz6z7PvuPgnMpxhb12XNOa30xYrEeLjQW/z6Cj7n6C8Vb9zrueeTXPRJI4cYHwphwAAABojEk5AAAA0BiTcgAAAKAxYsqBs6QmvrKmIEfQz1IFhsLxaAxyd4x7GmjxmSDmvSI+1kX4VhT9CYvETCRGO4rHdjHb0XmXvoO4W4171z3VxRcH+9biPFGBIY3Rjq75tLtgjsb7a5EdMzPryTnVeOxofHrsUY5AtJ02kTG7OHkL8g8q+q2Rg1j5LlH8v94HeYnaPaElY7bzTK9nd27GUgWHgAuEN+UAAABAY0zKAQAAgMaYlAMAAACNEVMOnDdLxp3XrGXu+q7oJ1wfu2Nt9Zp1kcNYddkuWRDDreOL4s7VInp/Ue4rirXuGt/hl+XHmnXUdW3uIG45yXrndWtWd69lHsZja99RbLp+ocdZEbfvtjGruif9JsG1qvjduPsrjE2vWe//+WOtl1rb33xuxtIx3MvmrrhujicuH7goeFMOAAAANMakHAAAAGiMSTkAAADQGJNyAAAAoDESPYGLaMmCIcv1051I6RLoKoqnhAmaLmky2JkkqNUkX1qqSN4b+sepJmCm3hLnPUhudMmgUdKrJn9GiZQV+9LtwmI4NUmSk47rFyUldiQEh/uJEiD1GCoSiWuSJBc1Ba2iZFApvKNtFtNgfBXXr6agz7E5rmcIgD/Cm3IAAACgMSblAAAAQGNMygEAAIDGiCkHEFsyZnSZ2NxlYl9rYodrtgtj02timTU2vSLWOoxxd/1WFKjRYw/GlwYV51Rjq8PY9O7x+J0HBY+Gcm1k33GhmYrjrCoqVY6nJmchJH2H/ehxRf12FMuq6XfpeHFiwYGVxZtyAAAAoDEm5QAAAEBjTMoBAACAxogpB1CvItb6OGLIw3WkK9aajuOSO/qpGG/Vuuk18ewV46uJo3aC86nj03XCzerisZfJEYjWY1/sy5rtNbHp2u9o5L7LslZ4TWx6FNu/VC5EoO4+6Lj/j2nNdgBnC2/KAQAAgMaYlAMAAACNMSkHAAAAGmNSDgAAADRGoieAeseVXKb91CSQHlMBmK4ku6jvugTNblVFYVxhmaAQT0WyqiYu1hQhitospChSmMiohZRqEnWXSLBd7I8721SJ7q9Zxz0Z7Ksq2bii8FTV35PYCZx7vCkHAAAAGmNSDgAAADTGpBwAAABojJhyAO3VxMtWxFof175c3zXb1NSa6YoltqCIzZJFkpaJQV62YI6/NsH4OvIIauLtq4o4RbHzOp6K6xAXxpIvamK/l9wXgIuHN+UAAABAY0zKAQAAgMaYlAMAAACNEVMO4Gyoic09LicV43tcsekqODdLrZd9krHzHdevJg49zCPQNdKXjYuvscw9SLw4gEq8KQcAAAAaY1IOAAAANMakHAAAAGiMSTkAAADQGImeAM6GlglzNUViWqoZy2kewzEllZ5q4Z2a8a3SNQdw7vCmHAAAAGiMSTkAAADQGJNyAAAAoDFiygGgC7HEf2zZ2PRlzuFpnneuMYDGeFMOAAAANMakHAAAAGiMSTkAAADQGDHlAHDWLLPm93HFTBN7DQAngjflAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABoj0RMAzhqSLQHg3OFNOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgsZWclKeUPphSyof/9ydajwcAAAA4SSs3KU8p/SUz+2/NbLv1WAAAAIDTsFKT8pTSQ2b2Y2b2s2b2aOPhAAAAAKdipSblZvbPD/98d9NRAAAAAKdo0HoAz0op/Q0z+ytm9m0556dTSm0HBAAAAJySlZiUp5ReZmbvM7MP55z/zVfQz1EhL69dtk8AAADgpDUPX0kp9czsJ+0gsfPhxsMBAAAATt2xvClPKd00s5fdxyY/nXP+9sP//7vN7JvM7Ftyzs98JePIOb/hiPE9amav/0r6BgAAAE7KcYWvPGZm+/fR/otmZimlV5vZD5nZT+ScP3JMYwEAAADOlGOZlOec37rkpl9nZmtm9s6U0juPaPPpw6TPb/tK4s0BAACAVdU60fOmmX3wiL/7FjN7kZn9nJndPWwLAAAAnDtNJ+U5598xs3dFf5dSesQOJuXfl3P+zCkOCwAAADhVzVdfAQAAAC46JuUAAABAY61jyo+Uc/7m1mMAAAAATgNvygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjTEpBwAAABpjUg4AAAA0xqQcAAAAaIxJOQAAANAYk3IAAACgMSblAAAAQGNMygEAAIDGmJQDAAAAjaWcc+sxnLiU0tM961+/ZFuthwIAAIBzbMfu2cLmt3LOD97PdoOTGtCKubuwud2z2zdbD2QFvPbwz082HQVWDfcFItwXiHBfIMJ98cdebmZ373ejC/GmHH8spfSomVnO+Q2tx4LVwX2BCPcFItwXiHBffOWIKQcAAAAaY1IOAAAANMakHAAAAGiMSTkAAADQGJNyAAAAoDFWXwEAAAAa4005AAAA0BiTcgAAAKAxJuUAAABAY0zKAQAAgMaYlAMAAACNMSkHAAAAGmNSDgAAADTGpByFlNIHU0r58P/+ROvx4PSllF6dUvrelNKvpZQ+n1KapJSeTCn9Ykrpza3Hh5OXUvrqlNK/SCl9MaU0TindTCn9SErpgdZjw+lLKT2YUnpXSukXUkqfSSntpZTupJQ+llL6mykl5hIwM7OU0jueM4d4V+vxnDUUD8IfSSn9JTP738xs28wum9mrc86faTsqnLaU0v9qZn/NzD5hZh8zs1tm9hoz+1Yz65vZd+Wc399uhDhJKaVXmdnHzewFZvaLZvZJM/t6M3uzmf2+mb0p5/x0uxHitKWUvtPM/qmZPW5mHzWzz5nZC83sr5rZVTP7eTN7e2ZCcaGllF5qZv+PHfw7cdnM/lbO+cfbjupsYVIOMzNLKT1kBz+mR8zsRWb2Tcak/EJKKf0NM/tPOefflu+/ycx+1cyymb085/x4g+HhhKWUftnM3mZmD+ecf/Q53/8jM/tuM/tnOefvbDU+nL6U0lvM7JKZ/buc8+I537/IzP6jmb3UzP6bnPPPNxoiGkspJTv49+EVZvavzey/Nybl943/5IRn/fPDP9/ddBRoLuf8IZ2QH37/63bwP9pGZvaNpz0unLyU0ivtYEJ+08z+ifz1D5jZjpm9I6V06ZSHhoZyzr+Wc/63z52QH37/hJl94PDjN5/6wLBKHjazt5jZO+3gOYElMCnHs29G/4qZfSf/WRodpod/zpqOAiflLYd//kowAbtnZr9pZptm9sbTHhhWFs+ECy6l9Doz+2Eze1/O+Tdaj+csY1J+waWUXmZm7zOzD+ec/03j4WCFHd4rbzWzXTPjwXs+vebwz08d8fefPvzza09hLFhxKaWBmX3H4cdfajkWtHF4D/yUHeQZfF/j4Zx5g9YDQDuHGfM/aQeJnQ83Hg5WWEppzcx+2szWzOzv5JyfaTwknIyrh3/eOeLvn/3+2skPBWfAD5vZnzKzj+Scf7n1YNDEe83sz5jZn88577UezFnHm/Iz7nCpsnwf//fh52z+3XaQ0Pm3mGSdL1/hfaF99e3gTcibzOxnzewfnNZxYOWkwz9ZIeCCSyk9bGbfYwer87yj8XDQQErp6+3g7fg/zDn/h9bjOQ94U372PWZm+/fR/otmB2tRm9kPmdlP5Jw/chIDQ1NL3RfqcEL+YTN7u5n9KzP7dpY9O9eefRN+9Yi/vyLtcAGllN5tB2GPnzCzt+acbzUeEk7Zc8JWPmVm3994OOcGk/IzLuf81iU3/To7CEV4Z0rpnUe0+fTBKkf2bcSbny1fwX3xRw4fuj9jBxPynzGz78g5z7/SfrHSfv/wz6Nixl99+OdRMec451JK7zGzf2xmv2sHE/IvtR0RGrlsf/yc2D+cK6gfSyn9mB0kgL7ntAZ2ljEpv7humtkHj/i7b7GDtcp/zszuHrbFBZJSGtnBm/G/bGb/0szeqatx4Fz66OGfb0sp9WRN6i07CGHaM7PfajE4tJVS+l47iCP/HTP7L3POX247IjQ0tqPnEK+3gzjzj9nB/9AntKUSxYPgpJQeMYoHXViHSZ3/2sz+oh08dP87JuQXB8WDEEkpfb+Z/X0ze9TM3kbICo6SUvpBO6hrQPGg+8SbcgDqA3YwIf+ymX3BzN4b/KfJR3LOj5zyuHA6/raZfdzM3p9SequZ/Z6ZfYOZvdkOwlb+bsOxoYGU0l+3gwn53Mz+vZk9HDwTbuacP3TKQwPOFSblANQrDv+8YQfLXR3lkZMfCk5bzvmxlNKftYNJ2F+wg/+B9riZvd/M/h5vSC+kZ58JfTN7zxFtft3MPnQagwHOK8JXAAAAgMZYpxwAAABojEk5AAAA0BiTcgAAAKAxJuUAAABAY0zKAQAAgMaYlAMAAACNMSkHAAAAGmNSDgAAADTGpBwAAABojEk5AAAA0BiTcgAAAKAxJuUAAABAY0zKAQAAgMaYlAMAAACNMSkHAAAAGmNSDgAAADTGpBwAAABo7P8Hv4jwUPeWEucAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 248, "width": 370 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist3d.project(0, 1));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4: UHI\n", "\n", "Let's explore the boost-histogram UHI syntax. We will reuse the previous 2D histogram from part 3:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 252, "width": 394 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist3);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I can see that I want y from 0.25 to 0.75, in data coordinates:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 248, "width": 380 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist3[:, bh.loc(0.25) : bh.loc(0.75)]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What's the contents of a bin?" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "181.0" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist3[100, 87]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How about in data coordinates?" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "181.0" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist3[bh.loc(0.5), bh.loc(0.75)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note: to get the coordinates manually:\n", "> \n", "> ```python\n", "hist3.axes[0].index(.5) == 100\n", "hist3.axes[1].index(.75) == 87\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How about a 1d histogram?" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxQAAAHwCAYAAAA/ySksAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAABYlAAAWJQFJUiTwAAAt5UlEQVR4nO3de7BtVX0n+u9PCaAoR/RqhauxebQgMSZpwKB4RcWEqDE+WrxS3jZqEm3fiqSN1Rg52FKVGBsR4+OWuYi2Nw2GlNgaNRgeYkChgOrmJiIY5MREMbRiDvI6io77x5xbF6vXPmfvcfbZa+99Pp+qVfOsOceYc6y5zlx7fdeYc45qrQUAAKDHfebdAAAAYP0SKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0E2gAAAAuu0x7wawfVV1U5J9k2yZc1MAANjYDkhyW2vtwOVUEijWvn3vd7/7Pfiwww578LwbAgDAxnXdddflrrvuWnY9gWLt23LYYYc9+Oqrr553OwAA2MCOOOKIXHPNNVuWW881FAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6LbHvBsAAGxQmzftYPnW1WkHsEvpoQAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuxqEAAOZjepwK41LAuiRQAAArY0cD2QEbklOeAACAbgIFAADQTaAAAAC6CRQAAEA3gQIAAOgmUAAAAN0ECgAAoJtAAQAAdBMoAACAbgIFAADQTaAAAAC6CRQAAEA3gQIAAOi2x7wbAACQJNm8aer51vm0A1gWPRQAAEA3PRQAQJ/pHgVgt6SHAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBtRQJFVR1fVe+tqi9W1W1V1arqYzuoc3RVfaaqbq2qO6vq2qp6Y1Xddzt1XlJVV1bV7VW1taouqapnbaf8/arq1Kq6vqrurqpbqurjVXXYduo8oqrOqqpvVdW2qtpSVWdU1X4r+VoAAGAjWKkeircmeW2SX07yzR0VrqrnJLk0yTFJPpHkfUn2TPLuJOcsUuddSc5Osn+SDyX5WJLHJvlUVb12Rvm9knw+yduS3JbkPUn+OsnzklxVVUfNqHNwkquTvCzJlWN7vp7kDUm+VFUPWYnXAgAAG8VKBYoTkxySZN8kr9pewaraN0Mg+FGSp7TWfqe19h8yhJEvJTm+qk6YqnN0kpOS3JjkF1trJ7bWXpPkiCS3JnlXVR0wtak3JXlikvOSHNVa+/3W2ouSHJ/k/knOqqrp1//+JA9L8vrW2nNba29prR2bIRwcmuS0nX0tAACwkaxIoGitXdxa+1prrS2h+PFJHprknNbaVRPruDtDT0fyv4aSV47T01pr35uosyVDj8BeGXoVkiRVVRN13txa+/FEnU8m+WKSn0/y5Ik6ByU5LsnCOiedkuSOJC+uqn128rUAAMCGMY+Lso8dp5+bsezSJHcmOXo8ZWkpdT47VSZJDk7yyCQ3tNZuWmKdhX9fMBlAkqS19v0kl2Xo2Xj8Etu12GsBAIANYx6B4tBxesP0gtbaPUluSrJHkoOSZOwReHiS21trN89Y39fG6SFL2cZq1Zn1Wranqq6e9Ujy6B3VBQCAedljDtvcNE63LrJ8Yf6DOsuv9ToAsD5t3rTjMsBuZx6BYkdqnC7leoxJyynfs41dWqe1dsTMFQy9FIcvY5sAsDFMB5jNi/1+B8zTPE55Wvg0WOxnjn2nyu2o/KxeguVuYzXrAADAhjGPQHH9OD1kekFV7ZHkwCT3ZBj/Ia21OzKMbfGAqtp/xvoeNU4nr2NYdBurVWfWawEAgI1mHoHionH69BnLjslwJ6XLW2vblljnGVNlkmG8im8kOaSqDlxinYvH6XHT41NU1QMzjGlxV5IvL7Fdi70WAADYMOYRKM5L8p0kJ1TVkQszq2rvJO8Yn35gqs4Hx+nJVbXfRJ0DkrwmybYkH16YP46HsVDnnZMBYRzZ+klJvpLkCxN1bkxyQZKFdU46Nck+ST469pjszGsBAIANY0Uuyq6q5yZ57vj0Z8fpE6rq7PHf32mt/V6StNZuq6qXZ/gyfklVnZNhtOtnZ7gN63lJzp1cf2vt8qo6PcPo19dW1XlJ9kzywiQPTvK6cZC7SacneVaGweeuqKoLM4xN8YIM40P89vR4E0leneTyJGdW1dOSXJfkqCRPzXCq08lT7Vr2awEAgI1kpe7y9MtJXjI176D8dPyFf0jyewsLWmvnV9WTM3xBf36SvZP8fYbAcOasEbdbaydV1bVJXpvkFUl+nOSaJH/cWvv0jPLbqupXk7wlyYuSnJjktiTnJzmltfaVGXVuHHsa3p7hNKZnJrk5yZlJTm2t3TqjzrJfCwAAbBTl++7aVlVXH3744YdfffXV824KALu7eY9D4baxsEsdccQRueaaa65ZbDiDxczjGgoAAGCDECgAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALrtMe8GAABr1OZN827BvU23Z/PW+bQDuBc9FAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBtj3k3AACgy+ZNM+ZtXf12wG5OoAAABrO+oAPsgFOeAACAbgIFAADQTaAAAAC6CRQAAEA3gQIAAOg210BRVb9RVRdU1T9V1V1V9fWq+vOqesIi5Y+uqs9U1a1VdWdVXVtVb6yq+25nGy+pqiur6vaq2lpVl1TVs7ZT/n5VdWpVXV9Vd1fVLVX18ao6bDt1HlFVZ1XVt6pqW1Vtqaozqmq/5e0RAABYX+YWKKrqj5J8OsnhST6X5D1JrknynCSXVdW/myr/nCSXJjkmySeSvC/JnkneneScRbbxriRnJ9k/yYeSfCzJY5N8qqpeO6P8Xkk+n+RtSW4b2/TXSZ6X5KqqOmpGnYOTXJ3kZUmuHNvz9SRvSPKlqnrIEncJAACsO3MZh6KqfjbJ7yX55yS/2Fq7ZWLZU5NclOTtGQJAqmrfDIHgR0me0lq7apz/B2PZ46vqhNbaORPrOTrJSUluTPK41tr3xvl/nCEAvKuqPt1a2zLRtDcleWKS85K8sLX247HOuUnOT3JWVT12Yf7o/UkeluT1rbX3Tmz/9CQnJjktySv79xYAAKxd8+qh+Ffjtq+YDBNJ0lq7OMn3kzx0Yvbx4/NzFsLEWPbuJG8dn75qahsLX+JPWwgTY50tGXo39srQq5AkqaqaqPPmydDQWvtkki8m+fkkT56oc1CS45IsrHPSKUnuSPLiqtpnxj4AAIB1b16B4mtJfpDkV6rqf5tcUFXHJHlghlONFhw7Tj83Y12XJrkzydHjKUtLqfPZqTJJcnCSRya5obV20xLrLPz7gqlei7TWvp/ksiT3T/L4GesDAIB1by6nPLXWbq2q309yepKvVNX5Sb6b4Uv9szNcx/DvJ6ocOk5vmLGue6rqpiSPSXJQkuvGHoGHJ7m9tXbzjCZ8bZwespRt7GSd48Y6Fy5SJklSVVcvsujR26sHAADzNJdAkSSttTOqakuSs5K8fGLR3yc5e+pUqE3jdOsiq1uY/6DO8qtZBwAANox53uXpzRkufj47Q8/EPkmOyHCHpP+3qt65nNWN07bMZiynfM82llyntXbErEeSry5jewAAsKrmEiiq6ilJ/ijJf2utvam19vXW2p2ttWsy3KL1m0lOGi96Tn76S/+m/2Vlg32nyu2o/KyeheVuo7cOAABsGPPqoVgYWO7i6QWttTszjOdwnyT/Zpx9/Tg9ZLp8Ve2R5MAk92To3Uhr7Y4MoeQBVbX/jO0/apxOXvuw6DZWuA4AAGwY8woUC3djeugiyxfm/2CcXjROnz6j7DEZ7qR0eWtt28T87dV5xlSZZBiv4htJDqmqA5dYZyEQHVdV99qXVfXADGNa3JXkyzPWBwAA6968AsUXx+krqurhkwuq6hkZvojfneTycfZ5Sb6T5ISqOnKi7N5J3jE+/cDUNj44Tk+uqv0m6hyQ5DVJtiX58ML81lqbqPPOyYAwjtL9pCRfSfKFiTo3JrkgycI6J52a4bqQj449JgAAsOHM6y5P52UYZ+JXM9zm9RNJvp3ksAynQ1WSt7TWvpskrbXbqurlY71LquqcJLdmuMXsoeP8cyc30Fq7fByt+k1Jrq2q85LsmeSFSR6c5HVTo2Qnw21sn5VhIL0rqurCDGNTvCDDWBe/PT3eRJJXZwg+Z1bV05Jcl+SoJE/NcKrTyb07CQAA1rq59FCMX8qfmeTEDL/6Py/JSRkGgPtMkl9vrb1nqs75GUapvjTJ85O8LskPMwSGE8YehuntnJTkpRnCyiuS/FaSv0vym621P5lRfluGkPP2DLd6PTHJryU5P8njWmtXzKhzY5IjM9yt6qjxdRyc5MwkT1gIRQAAsBHNcxyKHyY5Y3wstc5lGYLIcrbzkSQfWUb5u5KcMj6WWucfk7xsOe0CAICNYG7jUAAAAOufQAEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0G2PeTcAAJiTzZvm3QJgA9BDAQAAdNNDAQBsHNO9Lpu3zqcdsBvRQwEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHTbY94NAABWyeZN824BsAEJFADAxjUdojZvnU87YANzyhMAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBt7oGiqp5UVX9RVTdX1bZxekFVPXNG2aOr6jNVdWtV3VlV11bVG6vqvttZ/0uq6sqqur2qtlbVJVX1rO2Uv19VnVpV11fV3VV1S1V9vKoO206dR1TVWVX1rfE1bKmqM6pqv+XvEQAAWD/mGiiq6q1JLk1yTJLPJfnPST6VZL8kT5kq+5yJsp9I8r4keyZ5d5JzFln/u5KcnWT/JB9K8rEkj03yqap67YzyeyX5fJK3JbktyXuS/HWS5yW5qqqOmlHn4CRXJ3lZkivH9nw9yRuSfKmqHrK0vQEAAOvPHvPacFW9IMl/yvCF/d+21r4/tfxnJv69b4ZA8KMkT2mtXTXO/4MkFyU5vqpOaK2dM1Hn6CQnJbkxyeNaa98b5/9xhgDwrqr6dGtty8Rm35TkiUnOS/LC1tqPxzrnJjk/yVlV9diF+aP3J3lYkte31t47sf3Tk5yY5LQkr+zaSQAAsMbNpYeiqu6T5I+S3JnkRdNhIklaaz+ceHp8kocmOWchTIxl7k7y1vHpq6ZWsfAl/rSFMDHW2ZKhd2OvDL0KC22qiTpvngwNrbVPJvlikp9P8uSJOgclOS7JwjonnZLkjiQvrqp9pl8fAABsBPM65enoJAcm+UyS71XVb1TV71fVG6rqCTPKHztOPzdj2aUZgsnR4ylLS6nz2akySXJwkkcmuaG1dtMS6yz8+4KpXouMIemyJPdP8vgZ67uXqrp61iPJo3dUFwAA5mVepzw9bpz+c5JrMlzX8BNVdWmS41tr/3Ocdeg4vWF6Ra21e6rqpiSPSXJQkuvGHoGHJ7m9tXbzjO1/bZweMjFv0W3sZJ3jxjoXLlIGAADWrXkFioeN01cmuSnJrya5Ism/ynBh9q8n+fP89MLsTeN06yLrW5j/oM7yq1lnptbaEbPmj70Uh++oPgAAzMO8TnlauM1rZeiJuLC1dntr7e8y3FHpn5I8eZHTn2apcdqW2Y7llO/ZRm+7AABgXZhXoFi4SPrrrbX/MbmgtXZXkr8an/7KOF34pX9TZtt3qtyOys/qWVjuNnrrAADAhjGvQHH9OP2XRZYvBI77TZU/ZLpgVe2R4QLvezKM/5DW2h1JvpnkAVW1/4z1P2qcTl77sOg2VrgOAABsGPMKFJdmCACPqqo9Zyz/hXG6ZZxeNE6fPqPsMRnupHR5a23bxPzt1XnGVJlkGK/iG0kOqaoDl1jn4nF63Hgr3J+oqgdmGNPiriRfnrE+AABY9+YSKFpr30lyboZThd42uayqfi3DRdlb89Nbvp6X5DtJTqiqIyfK7p3kHePTD0xt5oPj9OSq2m+izgFJXpNkW5IPT7SpTdR552RAGEfpflKSryT5wkSdG5NckGRhnZNOTbJPko+OPSYAALDhzG2k7AyjUh+V4Qv/MUmuzHCXp+dlGBH75a21f0mS1tptVfXyDMHikqo6J8mtSZ6d4dat52UIKD/RWrt8HK36TUmurarzkuyZ5IVJHpzkdVOjZCfJ6UmelWEgvSuq6sIMY1O8IMNYF789Pd5EklcnuTzJmVX1tCTXja/rqRlOdTq5dwcBAMBaN69TntJauyXDF+93J/m5JK/PMFDcXyZ5Umvtz6fKn59hlOpLkzw/yeuS/DBDYDhh7GGY3sZJSV6a5NtJXpHkt5L8XZLfbK39yYzy2zLcwvbtGW71emKSX0tyfpLHtdaumFHnxiRHJjl7fD0nZRgk78wkT2itfXeJuwQAANadefZQpLV2a4ZA8KYllr8syTOXuY2PJPnIMsrfleSU8bHUOv+Y5GXLaRcAAGwEc+uhAAAA1j+BAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALrNdWA7AGAX2rxp3i0AdgN6KAAAgG56KACA3cd0r83mrfNpB2wgeigAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACg2x7zbgAAsEI2b5p3C4DdkEABAOy+pkPY5q3zaQesY055AgAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHQTKAAAgG4CBQAA0E2gAAAAugkUAABAN4ECAADoJlAAAADdBAoAAKCbQAEAAHRbM4Giql5cVW18/O4iZY6uqs9U1a1VdWdVXVtVb6yq+25nvS+pqiur6vaq2lpVl1TVs7ZT/n5VdWpVXV9Vd1fVLVX18ao6bDt1HlFVZ1XVt6pqW1Vtqaozqmq/5e0FAABYX9ZEoKiqn0vy3iS3b6fMc5JcmuSYJJ9I8r4keyZ5d5JzFqnzriRnJ9k/yYeSfCzJY5N8qqpeO6P8Xkk+n+RtSW5L8p4kf53keUmuqqqjZtQ5OMnVSV6W5MqxPV9P8oYkX6qqh+zo9QMAwHo190BRVZXkw0m+m+SDi5TZN0Mg+FGSp7TWfqe19h+S/HKSLyU5vqpOmKpzdJKTktyY5Bdbaye21l6T5IgktyZ5V1UdMLWpNyV5YpLzkhzVWvv91tqLkhyf5P5Jzqqq6X32/iQPS/L61tpzW2tvaa0dmyFYHJrktOXuEwAAWC/mHiiSvD7JsRl+4b9jkTLHJ3loknNaa1ctzGyt3Z3krePTV03VeeU4Pa219r2JOlsy9G7sNW4zyU+CzUKdN7fWfjxR55NJvpjk55M8eaLOQUmOS7KwzkmnjK/nxVW1zyKvCwAA1rW5BorxuoQ/TPKe1tql2yl67Dj93Ixllya5M8nR4ylLS6nz2akySXJwkkcmuaG1dtMS6yz8+4LJAJIkrbXvJ7ksQ8/G42esDwAA1r095rXhqtojyX9J8o0k/3EHxQ8dpzdML2it3VNVNyV5TJKDklw39gg8PMntrbWbZ6zva+P0kKVsYyfrHDfWuXCRMkmSqrp6kUWP3l49AACYp7kFigwXPv+bJP9Ha+2uHZTdNE63LrJ8Yf6DOsuvZh0AANgw5hIoqupXMvRK/OfW2pdWYpXjtC2z3nLK92xjyXVaa0fMXMHQc3H4MrYJAACrZtWvoZg41emGJH+wxGoLv/RvWmT5vlPldlR+Vs/CcrfRWwcAADaMeVyU/YAM1xQcluTuicHsWoY7IyXJh8Z5Z4zPrx+nh2TKGFAOTHJPhvEf0lq7I8k3kzygqvaf0YZHjdPJax8W3cYK1wEAgA1jHqc8bUvy/yyy7PAM11X8TYYv6wunQ12U5P9K8vQk/3WqzjEZ7qR0aWtt28T8i5K8eKzz4ak6z5gos+DGDBeIH1JVB86409OsOheP0+Oq6j6Td3qqqgdmGNPiriRfnvlqAQBgnVv1HorW2l2ttd+d9Ujy38ZiHxnnnTs+Py/Jd5KcUFVHLqyrqvZO8o7x6QemNrUwSN7JVbXfRJ0DkrwmQ7D5SdBorbWJOu+cHMBuHKX7SUm+kuQLE3VuTHJBkoV1Tjo1yT5JPjr2mAAAwIYzz7s8LVlr7baqenmGYHFJVZ2TYbTrZ2e4det5Sc6dqnN5VZ2eYfTra6vqvCR7Jnlhkgcned04yN2k05M8K8NAeldU1YUZxqZ4QYaxLn57eryJJK9OcnmSM6vqaUmuS3JUkqdmONXp5J3fAwAAsDathZGyl6S1dn6GUaovTfL8JK9L8sMMgeGEsYdhus5JSV6a5NtJXpHkt5L8XZLfbK39yYzy25L8apK3Z7jV64lJfi3J+Uke11q7YkadG5McmeTsDEHipAyD5J2Z5Amtte/2vmYAAFjr1lQPRWttc5LN21l+WZJnLnOdH0nykWWUvyvDxeGn7KjsRJ1/TPKy5bQLAAA2gjUVKAAA5mrz1J3gN7vzO+yIQAEA69X0l1+AOVg311AAAABrj0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB022PeDQAAlmDzpnm3YPc0vd83b51PO2AN00MBAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6CZQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFAAAQDeBAgAA6LbHvBsAAMywedO8WwCwJAIFAMBSTQe9zVvn0w5YQ5zyBAAAdBMoAACAbgIFAADQTaAAAAC6CRQAAEA3gQIAAOg2l0BRVQ+pqt+tqk9U1d9X1V1VtbWq/qaqfqeqZrarqo6uqs9U1a1VdWdVXVtVb6yq+25nWy+pqiur6vZxG5dU1bO2U/5+VXVqVV1fVXdX1S1V9fGqOmw7dR5RVWdV1beqaltVbamqM6pqv+XtGQAAWF/m1UPxgiQfSnJUkiuSnJHkL5L8QpI/TfLxqqrJClX1nCSXJjkmySeSvC/JnkneneScWRupqnclOTvJ/uP2PpbksUk+VVWvnVF+rySfT/K2JLcleU+Sv07yvCRXVdVRM+ocnOTqJC9LcuXYnq8neUOSL1XVQ5a0RwAAYB2a18B2NyR5dpK/bK39eGFmVf3HDF/Kn5/k32YIGamqfTMEgh8leUpr7apx/h8kuSjJ8VV1QmvtnIl1HZ3kpCQ3Jnlca+174/w/zhAA3lVVn26tbZlo15uSPDHJeUleuNC2qjo3yflJzqqqx062Ocn7kzwsyetba++d2P7pSU5MclqSV/bvKgAAWLvm0kPRWruotfapqS/maa19O8kHx6dPmVh0fJKHJjlnIUyM5e9O8tbx6aumNrPwJf60hTAx1tmSoXdjrwy9CkmSsUdkoc6bJ9vWWvtkki8m+fkkT56oc1CS45IsrHPSKUnuSPLiqtpneh8AAMBGsBYvyv7hOL1nYt6x4/RzM8pfmuTOJEePpywtpc5np8okycFJHpnkhtbaTUuss/DvC2aEo+8nuSzJ/ZM8fsb6AABg3ZvXKU8zVdUeSX5rfDoZBA4dpzdM12mt3VNVNyV5TJKDklw39gg8PMntrbWbZ2zqa+P0kKVsYyfrHDfWuXCRMkmSqrp6kUWP3l49AACYp7XWQ/GHGS7M/kxr7a8m5m8ap1sXqbcw/0Gd5VezDgAAbBhrpoeiql6f4SLqryZ58XKrj9O2zHrLKd+zjSXXaa0dMXMFQ8/F4cvYJgAArJo10UNRVa/JcIvWryR5amvt1qkiC7/0b8ps+06V21H5WT0Ly91Gbx0AANgw5h4oquqNSf4kyd9mCBPfnlHs+nF6yPSC8bqLAzNcxP31JGmt3ZHkm0keUFX7z1jfo8bp5LUPi25jhesAAMCGMddAUVW/n2EguP+eIUzcskjRi8bp02csOybDnZQub61tW2KdZ0yVSYbxKr6R5JCqOnCJdS4ep8dNj+5dVQ/MMKbFXUm+PGN9AACw7s0tUIyD0v1hhkHmntZa+852ip+X5DtJTqiqIyfWsXeSd4xPPzBVZ2E8i5Orar+JOgckeU2SbUk+vDC/tdYm6rxzMiCMo3Q/KcMpWV+YqHNjkguSLKxz0qlJ9kny0bHHBAAANpy5XJRdVS9J8vYMI19/Mcnrh3Hl7mVLa+3sJGmt3VZVL88QLC6pqnOS3JphtO1Dx/nnTlZurV0+jlb9piTXVtV5SfZM8sIkD07yuqlRspPk9CTPyjCQ3hVVdWGGsSlekGGsi9+eHm8iyauTXJ7kzKp6WpLrkhyV5KkZTnU6eVk7B4Dd0+bFLsdjTZt+3za7bJLdz7zu8rRwStF9k7xxkTJfSHL2wpPW2vlV9eQMX9Cfn2TvJH+fITCcOfYw3Etr7aSqujbJa5O8IsmPk1yT5I9ba5+eUX5bVf1qkrckeVGSE5PcluT8JKe01r4yo86NY6/J2zOcXvXMJDcnOTPJqTMuMAeW6YC3/OW9nm/5w9+YU0sAgGk143s4a0hVXX344YcffvXVi417B6tr+sv9WiBgsCHoodgQDrj7z7a73OcVa9kRRxyRa6655prFhjNYzJoZhwJYm9ZigJi2ozb6Aw6sFXpc2YgECuBe1kOAWC5/wIG1armfuT6/WIsECtjNbcQAsSMCBgCsHIECdjO7Y4DYEQEDWC98XrEWCRSwwQkQy+eaDGC9EDBYCwQK2GAECABgNQkUsM4JEAAsWMrfBL0YrDSBAmCZnBIFrGdOk2KlCRSwzuiRAADWEoEC1jgBYv3x6x+wnvjMYmcJFLDGCBAbjz/WAGxkAgUAAD/hOjGWS6AAWGV6LADYSAQKmDOnODHr/4CQAevTlr1fdK/nB9z9Z3NqCawegQIA5mHzpnm3ALroZWWaQAGrTI8ES+EPNrBe+LxCoIBdTIBgJfiDDcBaJVDAChIeWC0CBgBrhUABAMCKcdvZ3Y9AATtBjwRrhR4LAOZFoIBlECBYLwQMAFaLQAEAwKrxg8fGI1DAduiRYKPwBxxYq3w+rX8CBcBuyB9wAFaKQAET9EgAwHz5wWP9ESgAcJtHALoJFOzW9EgAwNqmx2LtEygA2CF/0FfA5k3zbgFzsGXvF93r+QF3/9mcWrJx+DxaewQKdit6JGBlOEUKgAUCBRuaAAHzsTv+grjDkLX3KjUEdjO74+fNWiNQALDLbYQeDT9QAMwmULCh+IMPALs3PRarT6AAYO5W4seA6S8NO/pS4QcIgJUhULCu+UIALNjR54HPC9g96bHY9e4z7wYAAADrlx4K1hW/MAIAO2PWdwm9FjtHoGBNEyAAgF3NaVE7R6BgTREggI1ieoRkSIyczcYkUAAAwAQ9FssjUDBXeiQAgLVOwNg+d3kCAAC66aFgVemRAADWOz0W9yZQsEsJEADARre7BwynPAEAAN30ULCi9EgAALu73a3HQqAAgBVg3Al6GJdi97DRA4ZAQTe9EQAAy7fRAoZAwaIEBgAAdkSgAACAOVrvPRbu8gQAAHQTKAAAgG5OeQKADu7qxK7grk+sR3ooAACAbgIFAADQTaAAAAC6uYYCAJbANRMAswkUAABrlIu0WQ+c8gQAAHQTKAAAgG5OeVohVfWIJG9P8vQkD0lyc5Lzk5zaWvveHJsGQAfXTLAWOQWKtUigWAFVdXCSy5M8LMknk3w1ya8keUOSp1fVE1tr351jEwGADUjAYC0QKFbG+zOEide31t67MLOqTk9yYpLTkrxyTm0DYAn0SAD0ESh2UlUdlOS4JFuSvG9q8SlJXpHkxVV1UmvtjlVuHgCLECDYiPRYMA8Cxc47dpxe0Fr78eSC1tr3q+qyDIHj8UkuXO3GATAQINgdzfp/L2Sw0gSKnXfoOL1hkeVfyxAoDsl2AkVVXb3Iol+67rrrcsQRR/S3sNPN39y66tsESJJfuM9NK77O1f8UhbXpIXn2vZ7/7Y8PnFNLWMwRn3/bXLZ73XXXJckBy60nUOy8TeN0sW/fC/Mf1Ln+H911111br7nmmi2d9Xs9epx+dZW3y/Z5X9Ye78kucM3Or8L7svZ4T9amRyc3Jt6XteTR1/xzkvm8JwckuW25lQSKXa/GadteodbamvrxbKHHZK21a3fnfVl7vCdrk/dl7fGerE3el7VnPb4nBrbbeQs9EJsWWb7vVDkAANgwBIqdd/04PWSR5Y8ap4tdYwEAAOuWQLHzLh6nx1XVvfZnVT0wyROT3JXky6vdMAAA2NUEip3UWrsxyQUZLmJ5zdTiU5Psk+SjxqAAAGAjclH2ynh1ksuTnFlVT0tyXZKjkjw1w6lOJ8+xbQAAsMtUa9u9+RBLVFU/l+TtSZ6e5CFJbk5yfpJTW2u3zrFpAACwywgUAABAN9dQAAAA3QQKAACgm0ABAAB0EygAAIBuAgUAANBNoAAAALoJFLu5qvqZqnpDVX24qv57Vf2gqlpV/W7Hug4Y6y72OGdXvIaNaCXfl4l1Hl1Vn6mqW6vqzqq6tqreWFX3Xcm2b3QrsR8dK8tXVY+oqrOq6ltVta2qtlTVGVW13zzWw2Al9udYZ7Fj4du7sv0bTVUdX1XvraovVtVt4z78WOe6HCsrYKXek7V+nBgpm32SnDH++5+TfDvJz+3kOv9HhkH9pv3tTq53d7Ki70tVPSfJXyS5O8m5SW5N8ptJ3p3kiUlesBNt3W3sgv3oWFmCqjo4yeVJHpbkk0m+muRXkrwhydOr6omtte+u1noYrPD+3JqffuZNun0Fmro7eWuSX8qw3/4pyaN7VuJYWVEr8p6M1u5x0lrz2I0fSfZM8owk+4/PNydpSX63Y10HjHXPnvfrWu+PFX5f9k1yS5JtSY6cmL93hj8YLckJ837Na/2xkvvRsbLsff9X4/563dT808f5H1zN9Xis+PuyJcmWeb+ejfBI8tQkj0pSSZ4yvg8fm9d767Gi78maPk6c8rSba639oLX22dbazfNuCz+1wu/L8UkemuSc1tpVE9u4O8MvJ0nyqhXYzkZnP85BVR2U5LgMf0zfN7X4lCR3JHlxVe2zGuthYH+uTa21i1trX2vjN9Ae3tuVtRLvyXrglCd2hf+9qv59kock+W6SL7XWrp1zm3Znx47Tz81YdmmSO5McXVV7tda2rV6z1p1dsR8dKzu2sN8vaK39eHJBa+37VXVZhi8/j09y4Sqsh8FK78+9qurfJXlkhi+s1ya5tLX2oxVsM0vjWFm71uxxIlCwK/za+PiJqrokyUtaa9+YS4t2b4eO0xumF7TW7qmqm5I8JslBSa5bzYatM7tiPzpWdmzR/T76WoYvN4dk+19uVmo9DFZ6f/5skv8yNe+mqnpZa+0LfU2kk2Nl7Vqzx4lTnlhJdyb5T0mOSLLf+HhykosznDd4oS7Sudg0Trcusnxh/oN2fVPWtZXcj46VpVup/e44WFkruT8/nORpGb4s7ZPksUn+7wzXGn22qn6pu5X0cKysTWv6OBEoNoAd3Eps1qPrFnI70lq7pbX2ttbaNa21fxkfl2b4JeOKJP86SfdtT9ebtfK+LKWp43RDn9+Z7PL3ZMn70bGyolbq/+9ucxyskuUcD6e21i5qrf1za+3O1trfttZemeEC4PtluCkFa4djZQ7W+nHilKeN4cYMt7Fcqm/tqobMMp4O8qdJjkpyTJL3rOb252itvC8LvyZtWmT5vlPlNrKdeU92+X7cjY+V7Vmp/e44WFmrsT8/mOSkDMcCq8exsr6sieNEoNgAWmtPm3cbluB/jtPd5jSONfS+XJ/kyAznu149uaCq9khyYJJ7knx99Zu2unbyPVmt/bjbHSs7cP04PWSR5Y8ap4ud773S62GwGvvzlnHqWFhdjpX1ZU0cJ055YrU8fpxu+C+ta9BF4/TpM5Ydk+T+SS53h6cdWq396Fi5t4vH6XFVda+/WVX1wAwDCt6V5MurtB4Gq7E/nzBOHQury7GyvqyJ40SgYNmqalNVPbqq9p+af1RV7Tmj/LFJThyfzus6gQ1vsfclyXlJvpPkhKo6cqL83kneMT79wCo1cz1b9n50rOy81tqNSS7IcOHha6YWn5rhV7mPttbuSJKq+plxnx+8M+th+1bqfamqx1TVg6fXX1X/KsmfjE8dC7uAY2XtWc/HSW3wcTZYgqp6S346FPwvZxgi/vIMt4ZLkr9prf3pRPmXZrjbwEdaay+dmH9JhttmXpJhePkk+cX89J7Wf9BaW/jixQ6s1PsyLntuhi/Edyc5J8mtSZ6d4faA5yX5Pzf6oDsrYbn70bGyMsY/rpcneViST2a4Le9RGUagvSHJ0a21745lD0hyU5J/aK0d0Lsedmwl3peq2pzkLRl+Fb8pyfeTHJzkNzKMQv+ZJM9rrf1gNV7Tejd+Rj13fPqzSX49wy/XXxznfae19ntj2QPiWNnlVuI9WRfHya4chttjfTwyfKlp23mcPVX+pYvM/50kn84wuubtSbYl+UaSc5M8ad6vc709Vup9mVj+xAwfOt/L0F39/2X4Nfy+836t6+mxnP3oWFnR/f5zGcLZzUl+kOQfMly0/uCpcgeM+3zLzqzHY3Xelwy3S/6vSb6a5F+S/DDDdUSfT/JbGX/49Fjy+7F5B383tkyUdaysk/dkPRwneigAAIBurqEAAAC6CRQAAEA3gQIAAOgmUAAAAN0ECgAAoJtAAQAAdBMoAACAbgIFAADQTaAAAAC6CRQAAEA3gQIAAOgmUAAAAN0ECgAAoJtAAQAAdBMoAACAbgIFAADQTaAAAAC6/f+hZqNQpHyWAQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 248, "width": 394 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist(hist3[:, :: bh.sum])\n", "plothist(hist3[:: bh.sum, :]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at one part and rebin:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 252, "width": 372 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist3[: 50 : bh.rebin(2), 50 :: bh.rebin(2)]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is the value at `(-.75, .5)`?" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1005.0" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist3[bh.loc(-0.75), bh.loc(0.5)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5: Understanding accumulators\n", "\n", "Boost-histogram has several different storages; storages store accumulators. Let's try making a profile." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Mean(count=3, value=0.4, variance=0.01)" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mean = bh.accumulators.Mean()\n", "mean.fill([0.3, 0.4, 0.5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's a quick example accessing the values:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "mean.count=3.0 mean.value=0.4 mean.variance=0.01\n" ] } ], "source": [ "print(f\"{mean.count=} {mean.value=} {mean.variance=}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6: Changing the storage" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "hist6 = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Mean())" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Regular(10, 0, 10), storage=Mean()) # Sum: Mean(count=3, value=0.4, variance=0.01)" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist6.fill([0.5] * 3, sample=[0.3, 0.4, 0.5])" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Mean(count=3, value=0.4, variance=0.01)" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist6[0]" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "MeanView(\n", " [(3., 0.4, 0.02), (0., 0. , 0. ), (0., 0. , 0. ), (0., 0. , 0. ),\n", " (0., 0. , 0. ), (0., 0. , 0. ), (0., 0. , 0. ), (0., 0. , 0. ),\n", " (0., 0. , 0. ), (0., 0. , 0. )],\n", " dtype=[('count', '" ] }, "metadata": { "image/png": { "height": 248, "width": 378 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "bins = [\n", " -10,\n", " -7,\n", " -4,\n", " -3,\n", " -2,\n", " -1,\n", " -0.75,\n", " -0.5,\n", " -0.25,\n", " 0,\n", " 0.25,\n", " 0.5,\n", " 0.75,\n", " 1,\n", " 2,\n", " 3,\n", " 4,\n", " 7,\n", " 10,\n", "]\n", "d7, e7 = np.histogram(data1 - 3.5, bins=bins, density=True)\n", "plt.hist(data1 - 3.5, bins=bins, density=True);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yes, it's ugly. Don't judge.\n", "\n", "We don't have a `.density`! What do we do? (note: `density=True` is supported if you do not return a bh object)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([3. , 3. , 1. , 1. , 1. , 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,\n", " 0.25, 0.25, 1. , 1. , 1. , 3. , 3. ])" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist7 = bh.numpy.histogram(data1 - 3.5, bins=bins, histogram=bh.Histogram)\n", "\n", "widths = hist7.axes.widths\n", "area = functools.reduce(operator.mul, hist7.axes.widths)\n", "\n", "area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yes, that does not need to be so complicated for 1D, but it's general." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "factor = np.sum(hist7.values())\n", "view = hist7.values() / (factor * area)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 248, "width": 378 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.bar(hist7.axes[0].centers, view, width=hist7.axes[0].widths);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8: Axis types\n", "\n", "There are more axes types, and they all provide the same API in histograms, so they all just work without changes:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "hist8 = bh.Histogram(\n", " bh.axis.Regular(30, 1, 10, transform=bh.axis.transform.log),\n", " bh.axis.Regular(30, 1, 10, transform=bh.axis.transform.sqrt),\n", ")" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(\n", " Regular(30, 1, 10, transform=log),\n", " Regular(30, 1, 10, transform=sqrt),\n", " storage=Double()) # Sum: 903807.0 (1000000.0 with flow)" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist8.reset()\n", "hist8.fill(*make_2D_data(mean=(5, 5), widths=(5, 5)))" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 252, "width": 375 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plothist2d(hist8);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9: And, circular, too!" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Regular(30, 0, 6.28319, circular=True), storage=Double()) # Sum: 300.0" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist9 = bh.Histogram(bh.axis.Regular(30, 0, 2 * np.pi, circular=True))\n", "hist9.fill(np.random.uniform(0, np.pi * 4, size=300))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, the really complicated part, making a circular histogram:\n" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 269, "width": 277 }, "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = plt.subplot(111, polar=True)\n", "plothist(hist9);" ] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.10" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/notebooks/MultiCellExample.ipynb000066400000000000000000000256071516627434300256150ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "id": "440ab15f-a5d9-46e8-966f-ae6ab9839d63", "metadata": {}, "source": [ "# MultiCell histograms" ] }, { "cell_type": "markdown", "id": "0ccfc0b8-2e1e-41c3-9d37-b84c7e89d13b", "metadata": {}, "source": [ "Usually, one has to deal with `n` events to histogram by sorting each event into bins based on some variable `x` and by adding the value of a `weight` to the value stored in that bin.\n", "\n", "However, there are also cases where one has to create multiple histograms for multiple independent weights that are all sorted into the same bin per event. Each event has one variable `x` and a list `weights` with a fixed number of `k` weights. A usage example could be to study a nominal weight and some systematic effects that manifest in variations of this weight without changing the the variable for selecting the bin (without changing `x`).\n", "\n", "In such a case, one option would be to create `k` histograms (one for each weight) and sort each event into these `k` histograms (each time providing a different weight). The downsides of this approach is, that one has to do the sorting of the events into the bins `k` times whereas one time would be sufficient. One also has to create `k` histogram objects.\n", "\n", "A more efficient approach would be to have only one histogram that stores a list of `k` weights per bin. This is achieved by using the `boost_histogram.storage.MultiCell` storage." ] }, { "cell_type": "code", "execution_count": 1, "id": "bb508fb8-440d-4349-941a-281216db2381", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "markdown", "id": "375102e8-14b6-4300-8ff8-516e05de0074", "metadata": {}, "source": [ "A `MultiCell` histogram `h` has to be provided with `k`, the fixed number of cells (in this example the weights) per bin, at construction time:" ] }, { "cell_type": "code", "execution_count": 2, "id": "3222b5d6-daec-4d83-8d1f-633fa5bae72f", "metadata": {}, "outputs": [], "source": [ "k = 10\n", "h = bh.Histogram(bh.axis.Integer(0, 7), storage=bh.storage.MultiCell(k))" ] }, { "cell_type": "markdown", "id": "420f519f-5821-4c0d-b673-f73ad3cd7a9c", "metadata": {}, "source": [ "To fill the `MultiCell` histogram with `n` events, one has to provide a 2-dimensional array of weights with the shape `(n, k)` via the `weight` keyword of the `fill()` method:" ] }, { "cell_type": "code", "execution_count": null, "id": "bb4f10fb-7c10-41ef-8c22-908fc751633f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Integer(0, 7), storage=MultiCell(0)) # Sum: [210.0, 217.0, 224.0, 231.0, 238.0, 245.0, 252.0, 259.0, 266.0, 273.0] ([12250.0, 12300.0, 12350.0, 12400.0, 12450.0, 12500.0, 12550.0, 12600.0, 12650.0, 12700.0] with flow)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n = 50\n", "x = np.arange(n)\n", "weights = np.arange(n * k).reshape(n, k)\n", "h.fill(x, weight=weights)" ] }, { "cell_type": "markdown", "id": "d6e8b3a4-460c-4d85-8ecf-87f3dc0c849a", "metadata": {}, "source": [ "Now, each bin of `h` stores a list of `k` weights:" ] }, { "cell_type": "code", "execution_count": 31, "id": "e0b97769-cfe2-4323-86ae-33bf37ae6b07", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h[1]" ] }, { "cell_type": "markdown", "id": "0310baa6-a38b-4c29-a4b0-d9506b2817a6", "metadata": {}, "source": [ "One can interact with a `MultiCell` identical to any other histogram with a different storage type.\n", "\n", "However, 2 important keypoints to remember:\n", "1. A `view` of a `MultiCell` histogram has the cells (weights) as first dimension. In our case, `h.view()` will have the shape `(k, 7)` with `7` being the number of bins from the `bh.axis.Integer(0, 7)` axis.:" ] }, { "cell_type": "code", "execution_count": 32, "id": "ee5b984f-5549-43ff-b4f6-bd828d1c3255", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0., 10., 20., 30., 40., 50., 60.],\n", " [ 1., 11., 21., 31., 41., 51., 61.],\n", " [ 2., 12., 22., 32., 42., 52., 62.],\n", " [ 3., 13., 23., 33., 43., 53., 63.],\n", " [ 4., 14., 24., 34., 44., 54., 64.],\n", " [ 5., 15., 25., 35., 45., 55., 65.],\n", " [ 6., 16., 26., 36., 46., 56., 66.],\n", " [ 7., 17., 27., 37., 47., 57., 67.],\n", " [ 8., 18., 28., 38., 48., 58., 68.],\n", " [ 9., 19., 29., 39., 49., 59., 69.]])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h.view()" ] }, { "cell_type": "markdown", "id": "9dc4980d-591c-4561-be64-e94b426734d9", "metadata": {}, "source": [ "To get the histogram corresponding to the weight with index 3 one can use `h.view()[3]`" ] }, { "cell_type": "markdown", "id": "b7d09f9d-d567-416e-9d21-0f8ae1e65db5", "metadata": {}, "source": [ "2. `MultiCell` histograms do NOT track the variances of their cell contents (weights) (in contrast to histograms with the `Weight` storage). If required, one can track the variances by adding them as another weight to the histogram: " ] }, { "cell_type": "code", "execution_count": null, "id": "4d592b6b-766d-436a-ae45-f43aa19c97e1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Integer(0, 5), storage=MultiCell(0)) # Sum: [100.0, 105.0, 110.0, 115.0, 120.0, 125.0, 130.0, 135.0, 140.0, 145.0, 3000.0] ([12250.0, 12300.0, 12350.0, 12400.0, 12450.0, 12500.0, 12550.0, 12600.0, 12650.0, 12700.0, 4042500.0] with flow)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h = bh.Histogram(bh.axis.Integer(0, 5), storage=bh.storage.MultiCell(k + 1))\n", "weights = np.zeros((n, k + 1))\n", "weights[:, :-1] = np.arange(n * k).reshape(n, k)\n", "weights[:, -1] = weights[:, 0] ** 2\n", "h.fill(x, weight=weights)" ] }, { "cell_type": "markdown", "id": "6e2ab208-de48-4ec2-987e-ef11a5863ce8", "metadata": {}, "source": [ "Now, the weight with index `k + 1` is the square of the weight with index `0` and therefore tracks the sum-of-weights-squared of the first weight." ] }, { "cell_type": "markdown", "id": "1eb9fcbf-36b0-41be-b78e-1b44ae17a4f2", "metadata": {}, "source": [ "## Benchmarks" ] }, { "cell_type": "markdown", "id": "69cadcb6-11b6-4fd0-97c9-ec3a2a8871ef", "metadata": {}, "source": [ "Let's estimate how much time one can save with `MultiCell` histograms in a 'real world' example in contrast to using multiple histograms with `Weight` and `Double` storage.\n", "\n", "For a precicsion analysis we have to process 150 million events. We can fill 10000 events per iteration. Each event has 2000 different weights due to systematic variations. We also have some reweighting that increases the number of weights by a factor 10 to 20000 weights. The events are sorted based on two variables into a `8 x 8` histogram that uses `variable` axis.\n", "\n", "\n", "We are only interested in the sum-of-weights-squared for the nominal weight (which would be 10 out of these 20000 in this case), therefore we will use histograms with a `Double` storage type as comparison to the `MultiCell` histogram." ] }, { "cell_type": "code", "execution_count": null, "id": "b8defdfc-bf77-4c54-9123-b8c283730162", "metadata": {}, "outputs": [], "source": [ "n_total = 150_000_000\n", "n_batch = 10_000\n", "k = 20_000\n", "axes = [\n", " bh.axis.Variable(np.linspace(-1, 1, 9)),\n", " bh.axis.Variable(np.linspace(-1, 1, 9)),\n", "]\n", "h_MultiCell = bh.Histogram(*axes, storage=bh.storage.MultiCell(k))\n", "h_doubles = [bh.Histogram(*axes, storage=bh.storage.Double()) for i in range(k)]" ] }, { "cell_type": "code", "execution_count": 51, "id": "ea43b439-c4e3-437c-bcbe-fba1d010d717", "metadata": {}, "outputs": [], "source": [ "rng = np.random.default_rng()\n", "variables = rng.normal(size=(2, n_batch))\n", "weights = rng.normal(size=(n_batch, k))" ] }, { "cell_type": "markdown", "id": "7c39a95f-6050-4edb-96e3-155bd3360341", "metadata": {}, "source": [ "Filling the `Double` type histograms:" ] }, { "cell_type": "code", "execution_count": 53, "id": "9ebdb94b-a39c-4748-bc5b-2ba7fe7bb51d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "26.7 s ± 1.58 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit\n", "for index, hist in enumerate(h_doubles):\n", " hist.fill(variables[0], variables[1], weight=weights[:, index])" ] }, { "cell_type": "markdown", "id": "c9d5f4e8-2242-4760-92b2-f5f2070c982c", "metadata": {}, "source": [ "Filling the `MultiCell` histogram:" ] }, { "cell_type": "code", "execution_count": null, "id": "a79cec31-a319-4c0e-86ff-4f73276059c3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "325 ms ± 60.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit\n", "h_MultiCell.fill(variables[0], variables[1], weight=weights)" ] }, { "cell_type": "markdown", "id": "a989034f-be9e-4f04-b7e7-ba3eaa112688", "metadata": {}, "source": [ "On my test laptop, filling the `k` `Double` type histograms took `26.7 +- 1.6 s` whereas filling the `MultiCell` histogram only took `325 +- 60 ms` which is `82` times faster.\n", "\n", "Therefore, using `MultiCell` histograms instead of multiple `Double` type histograms for filling all 150 million events would save us `110` hours (`1.4 h` for the `MultiCell` filling vs. `111 h` for iterated `Double` type filling)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.12" } }, "nbformat": 4, "nbformat_minor": 5 } scikit-hep-boost-histogram-5f6f3e3/notebooks/PerformanceComparison.ipynb000066400000000000000000000206401516627434300266730ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Performance Comparison\n", "\n", "We will compare boost-histogram to numpy." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from numpy.testing import assert_allclose\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "threads: 8\n" ] } ], "source": [ "import os\n", "\n", "threads = os.cpu_count() // 2\n", "print(f\"threads: {threads}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Testing setup\n", "\n", "This is just a simple 1D and 2D dataset to use for performance runs. The testing setup is the same as \"MBP\" in [this post](https://iscinumpy.gitlab.io/post/histogram-speeds-in-python/), a dual-core MacBook Pro 2015." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "bins = (100, 100)\n", "ranges = ((-3, 3), (-3, 3))\n", "bins = np.asarray(bins).astype(np.int64)\n", "ranges = np.asarray(ranges).astype(np.float64)\n", "\n", "edges = (\n", " np.linspace(*ranges[0, :], bins[0] + 1),\n", " np.linspace(*ranges[1, :], bins[1] + 1),\n", ")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "np.random.seed(42)\n", "vals = np.random.normal(size=[2, 10_000_000]).astype(np.float32)\n", "vals1d = np.random.normal(size=[10_000_000]).astype(np.float32)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Traditional 1D NumPy Histogram\n", "\n", "This is reasonably optimized; it should provide good performance." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "answer, e = np.histogram(vals1d, bins=bins[0], range=ranges[0])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "74.5 ms ± 2.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "h, _ = np.histogram(vals1d, bins=bins[0], range=ranges[0])\n", "assert_allclose(h, answer, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram 1D\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "41.6 ms ± 712 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist = bh.Histogram(bh.axis.Regular(bins[0], *ranges[0]), storage=bh.storage.Int64())\n", "hist.fill(vals1d)\n", "assert_allclose(hist, answer, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram 1D NumPy clone" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "43.1 ms ± 769 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "h, _ = bh.numpy.histogram(vals1d, bins=bins[0], range=ranges[0])\n", "assert_allclose(h, answer, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram in 1D, threaded" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "13.3 ms ± 153 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist = bh.Histogram(bh.axis.Regular(bins[0], *ranges[0]), storage=bh.storage.Int64())\n", "\n", "hist.fill(vals1d, threads=threads)\n", "assert_allclose(hist, answer, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram 1D NumPy clone, threaded" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "13.8 ms ± 238 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit\n", "h, _ = bh.numpy.histogram(vals1d, bins=bins[0], range=ranges[0], threads=threads)\n", "assert_allclose(h, answer, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Traditional 2D NumPy histogram\n", "\n", "Not as well optimized for regular filling." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "answer2, *ledges = np.histogram2d(*vals, bins=bins, range=ranges)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "874 ms ± 22.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%%timeit\n", "H, *ledges = np.histogram2d(*vals, bins=bins, range=ranges)\n", "assert_allclose(H, answer2, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram in 2D" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "77.6 ms ± 615 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist = bh.Histogram(\n", " bh.axis.Regular(bins[0], *ranges[0]), bh.axis.Regular(bins[1], *ranges[1])\n", ")\n", "hist.fill(*vals)\n", "assert_allclose(hist, answer2, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram 2D NumPy clone" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "84.7 ms ± 2.78 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "H, *ledges = bh.numpy.histogram2d(*vals, bins=bins, range=ranges)\n", "assert_allclose(H, answer2, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram in 2D, threaded" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "28.7 ms ± 708 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist = bh.Histogram(\n", " bh.axis.Regular(bins[0], *ranges[0]), bh.axis.Regular(bins[1], *ranges[1])\n", ")\n", "\n", "hist.fill(*vals, threads=threads)\n", "assert_allclose(hist, answer2, atol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Boost histogram 2D NumPy clone, threaded" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "29.6 ms ± 503 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "H, *ledges = bh.numpy.histogram2d(*vals, bins=bins, range=ranges, threads=threads)\n", "assert_allclose(H, answer2, atol=1)" ] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/notebooks/SimpleExample.ipynb000066400000000000000000000065001516627434300251430ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Simple Example\n", "\n", "Let's try some basic functionality of boost-histogram:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "vals = np.random.normal(size=(2, 1_000_000))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(\n", " Regular(10, 0, 10, metadata='x', transform=sqrt),\n", " Regular(10, 0, 1, circular=True, metadata='y'),\n", " storage=Int64())" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist = bh.Histogram(\n", " bh.axis.Regular(10, 0, 10, metadata=\"x\", transform=bh.axis.transform.sqrt),\n", " bh.axis.Regular(10, 0, 1, circular=True, metadata=\"y\"),\n", " storage=bh.storage.Int64(),\n", ")\n", "hist" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This fills the histogram" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(\n", " Regular(10, 0, 10, metadata='x', transform=sqrt),\n", " Regular(10, 0, 1, circular=True, metadata='y'),\n", " storage=Int64()) # Sum: 499682.0 (1000000.0 with flow)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hist.fill(*vals)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's just take a quick look at the bin contents:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 3955 3992 3972 3948 3930 3987 4073 3982 3878 4073]\n", " [11519 11675 11560 11528 11612 11575 11476 11643 11573 11603]\n", " [16197 16087 16261 16043 15775 15912 16067 16010 15973 15998]\n", " [12843 12956 12855 12912 12870 12940 12962 12891 12752 13073]\n", " [ 4807 4952 4844 4859 4869 4788 4657 4931 4775 4874]\n", " [ 636 618 598 608 633 644 603 635 611 645]\n", " [ 14 18 20 13 12 21 10 18 15 22]\n", " [ 0 0 0 0 1 0 0 0 0 0]\n", " [ 0 0 0 0 0 0 0 0 0 0]\n", " [ 0 0 0 0 0 0 0 0 0 0]]\n" ] } ], "source": [ "print(hist.view())" ] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/notebooks/ThreadedFills.ipynb000066400000000000000000000110741516627434300251120ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Threaded Fills" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from concurrent.futures import ThreadPoolExecutor\n", "from functools import reduce\n", "from operator import add\n", "\n", "import numpy as np\n", "from numpy.testing import assert_array_equal\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook explores parallel filling by hand (not using the `threads=` argument)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "hist_linear = bh.Histogram(bh.axis.Regular(100, 0, 1))\n", "hist_atomic = bh.Histogram(bh.axis.Regular(100, 0, 1), storage=bh.storage.AtomicInt64())\n", "\n", "vals = np.random.rand(10_000_000)\n", "hist_answer = hist_linear.fill(vals).copy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a traditional fill." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "25.5 ms ± 774 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist_linear.reset()\n", "hist_linear.fill(vals)\n", "assert_array_equal(hist_answer, hist_linear)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a single threaded atomic fill." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "59.9 ms ± 2.19 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist_atomic.reset()\n", "hist_atomic.fill(vals)\n", "assert_array_equal(hist_answer, hist_atomic)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a threaded fill (storage not threadsafe, so will get the wrong result; just for comparison)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is a threaded fill, this time with atomics. It may not be faster, but is useful in situations where you are filling from multiple places in your code." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "61.2 ms ± 1.57 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%%timeit\n", "hist_atomic.reset()\n", "threads = 4\n", "with ThreadPoolExecutor(threads) as pool:\n", " for chunk in np.array_split(vals, threads):\n", " pool.submit(hist_atomic.fill, chunk)\n", "assert_array_equal(hist_answer, hist_atomic)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This makes four separate histograms, then fills them and adds at the end." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def fun(x):\n", " hist = bh.Histogram(bh.axis.Regular(100, 0, 1))\n", " return hist.fill(x)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "8.12 ms ± 90 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit\n", "threads = 4\n", "with ThreadPoolExecutor(threads) as pool:\n", " results = pool.map(fun, np.array_split(vals, threads))\n", "hist_quad = reduce(add, results)\n", "assert_array_equal(hist_answer, hist_quad)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The expense of creating the histogram and summing them must be significantly less than the cost of filling for this to be faster." ] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/notebooks/aghast.ipynb000066400000000000000000001245221516627434300236520ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ROOT file format example\n", "\n", "To run this example, you will need `uproot`, which is another SciKit-HEP library." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import uproot\n", "\n", "import boost_histogram as bh\n", "\n", "demo_file = Path(\"demo_uproot_file.root\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[ROOT](https://root.cern/) is a modular scientific software toolkit used in High Energy Physics. (HEP) The ROOT file format is used to store almost all HEP data. This notebook will illustrate one method for converting to/from the ROOT file format using uproot, a Python implementation of a ROOT file reader and writer.\n", "\n", "For more complicated histograms, you may need Aghast and PyROOT, but that is a much heaver dependency, and is covered [in a separate tutorial](https://scikit-hep.org/scikit-hep-tutorials)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start by making a 1D histogram:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Histogram(Regular(15, -3, 3), storage=Double()) # Sum: 997277.0 (1000000.0 with flow)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h = bh.Histogram(bh.axis.Regular(15, -3, 3))\n", "h.fill(np.random.normal(size=1_000_000))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "with uproot.recreate(demo_file) as root_file:\n", " # Uproot automatically converts histograms\n", " root_file[\"hist\"] = h" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to save and load the ROOT histogram, use [uproot](https://github.com/scikit-hep/uproot) to read and write:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "with uproot.open(demo_file) as root_file_2:\n", " uproot_hist = root_file_2[\"hist\"]\n", "\n", "print(uproot_hist)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This uproot histogram can be converted directly to `boost_histogram`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABIUAAAM6CAYAAAAfd/ODAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAewgAAHsIBbtB1PgAAcbBJREFUeJzt3Qm8ndO9P/6ViCRiDGIegoip3PqRUENjdmUgUUPNVAxFSy8iSg0XRXHNVAipVtVQoiS9eqkpMUTQaxZTkKLEFCREJP/Xd93/3q+dc/Y+54RznHPyvN+v13nttffz7O+z9mkqOZ/zXevpMGfOnDkJAAAAgELp2NoTAAAAAOC7JxQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAOrX2BGjfvvjii/TMM8/kcY8ePVKnTv5IAQAAQHObNWtWev/99/N4/fXXT127dv3WNf0Ez7cSgVDfvn1bexoAAABQGBMmTEh9+vT51nUsHwMAAAAoIJ1CfCuxZKwyqVx++eVbdT4AAAAwP3rnnXfKK3Uqfxb/NoRCfCuVewhFILTSSiu16nwAAABgftepmfbztXwMAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABdSiodB7772X7rrrrnTKKaeknXbaKS299NKpQ4cO+evAAw/8RjXvueee/N5evXqlhRdeOC2++OKpd+/eabfddktXXnll+uyzzxp8//Tp09NvfvOb1KdPn7TkkkvmGmuvvXY69thj0xtvvNHkecS58Z54b9SIWlHzvPPOy9doqocffjjtu+++adVVV01du3ZNyy23XNpxxx3TjTfemOZFnL/DDjvk90edqBd1H3nkkXmqAwAAABRDhzlz5sxpseIdOtQ8dsABB6RRo0Y1udZHH32UDjrooHTHHXc0eN5TTz2Vvv/971c99sorr6T+/funl19+uerxxRZbLN1www1p4MCBDV7jzjvvzIHLtGnTqh6PkGrMmDE5uGrIaaedls4444w0e/bsqscHDBiQbr311hzy1DJjxowciI0dO7bq8Y4dO+ZQ7tRTT00tYcqUKWnllVfO47feeiuttNJKLXIdAAAAKLIpLfDz93e2fGyVVVbJnSzfxCeffJK23377ciA0ZMiQHN48+uij6fHHH0+33XZbOvrooxv8hnz66ac5ZCkFQocccki69957c6fOWWedlRZZZJEc8uy5557pH//4R4OhU5wT58Z74r1RI2pFzTBp0qR8rbhmLVdddVU6/fTTcyC0xhprpJEjR6YJEyak0aNHp6233jqfE8HST37ykwa/N3G8FAjF++L9USfqRd2oH+HTiBEjGqwDAAAAFMycFnTKKafMufPOO+e8++67+fnrr78eXUn564ADDmhynf322y+/p0uXLnPuuOOOmufNnj17zldffVX12K9+9avytX/zm9/UOz5+/Pg5nTp1ysf79etX8xpbbrllPifOffjhh+sdj9ql65x66qlVa3zwwQdzFl988XzOKqusMuf999+f6/isWbPmDBo0qFznvvvuq1rn3nvvLZ8T58f7KkXdqB/Hl1hiiTkffvjhnOb21ltvlecQYwAAAGBOu/j5u0U7haITJpZiLbvsst+4xrhx49Lvf//7PD7zzDPTzjvv3OBytU6dOtV7/auvvkqXXHJJHq+zzjp5L6C6Nttss3TwwQfn8QMPPJA7kOqKDpyHHnooj+PcH/zgB/XOidpxjXDxxRfna9d1zTXX5O6ncO655+a9liotsMAC6YorrsiPIfYpqub888/Pj/GZK88vibpRP3z88cf5ugAAAADt4u5jl112WX6MDaWPOuqob1TjvvvuK4cwsZdR7LNTTeXm17fffnu947E0qyT2N6omau+///7lICauXatO7GG06667Vq0TS+G22267PI6laXWXosXzeD3EebWWzkX9uE6tzwQAAAAUU5sOhWbOnFneRyj2FCptuPz111/nTZUmT56cvvjiiyZ1G5X069ev5nkbb7xx6tatWx6PHz++Zp2429hGG21Us07lNerWic8UHUchOo06d+7caJ0vv/wyTZw4ca5j0ckUtRr7TFF/0003Lb+nWucSAAAAUDxtOhT63//933Los/766+fNnY855pi8LCo2rl5ttdVyB1EERvfff3/NOs8//3x5HLeQryWWYZXuGPbCCy/UO156Lc6ptkyt2jXq1olNqCPUamwujdVp6meqPD5r1qyad15raHfzhr7eeeedeaoHAAAAtA21k402oDL4iLtoRSdP3VAjumXuueeevJTq7LPPTieccEK9OhFelDp8llhiiQavGbd3e/rpp9P777+fO3S6dOmSX49waurUqXnc2G3funfvnq/1+eef546manNpSp3SreZCc9ZZd911Gzy/1nsBAACA+Ueb7hT68MMPy+PYMDkCoX//93/Py68ipHnvvffSlVdembuF5syZk4YPH15eblaptB9P3EK+MRHmlHz22Wf1asxrncoa81qn1lyasw4AAABQTG26Uyg6bUoiBIplYnfddVf5Lls9evRIhx9+ePre976X99WJbqITTzwx36Es7kRW+d7Q0P49JaXOoDBjxox6Nea1TmWNea1Tay7NWacxdTuU6orlY3379p2nmgAAAEDra9OhUGlj6cpuobq3XQ9bbLFFvsvWrbfemvfeeeaZZ9IGG2xQr05pY+aGxJKxkoUWWqjqXOalTmWNea1Tay7NWacxjS1NAwAAANqnNr18bNFFFy2Poytoww03rHnujjvuWB7HXbaq1WnK0qnK7qTKZVmVc5mXOnWXds1LnVpzac46AAAAQDG16VCocpPjedlMOTaJrlR6b4QjH3/8cZOWS0UIVbnsKjpzllpqqXqbPFfz0UcflYOYuhs1V36OxupULt1qqToAAABAMbXpUGi99dYrj0u3ca+l8njd28VX3m3rxRdfrFkjbtn+6quv5vE666xT73ipziuvvJLPraXyGnXr9O7du7wErqG5NFanqZ+p8nh8X9Zcc80GzwUAAACKoU3vKbTqqqumVVZZJb355ptp8uTJ+Q5jlRtIVyqFOWHFFVest+dQyQMPPJA23XTTqjUmTpxY7vDZfPPN6x2POg899FA+54knnkibbLJJ1TpxjZK6dWJT6NiY+ZFHHslfsR9QrY2iS3WiY2njjTee61ifPn3y++L9cV7cea2aOP7oo4+W37PgggtWPQ8AaD09h49JRTL5nAGtPQUAoK13CoUf/ehH+XHatGnp3nvvrXnebbfdVjUECltttVW+bX343e9+l8OlakaNGlUeDxkypN7xwYMHl8fXXXdd1RpxB7Trr78+j5dYYom09dZb16wTn6ly3pViSdg999yTx9tuu+1cewiFeB6vhziv1hKyqB/XqfWZAAAAgGJq86HQMcccU77T1n/8x3+UA45Kf/jDH9L999+fxwMGDKi3b0501Pz85z/P47g72fnnn1+vRnTtjBw5Mo/j9vbRVVNXdPhsueWWeRznxnvquuCCC/I1wtFHH121M2fo0KHlkCo6fD744IN6S+GOOOKI8pK4448/vur35rjjjsuPsZTtyCOPrLfEburUqemEE04oB1RxXQAAAIDQYU6ttplmMG7cuLz/TmVIUQo4YllV3ZDiwAMPrFrnvPPOS8OGDcvjtdZaKwcdccv5UqfNlVdemQORxRZbLC8Bq7ZvzqeffpqXYE2aNCk/P/TQQ9OPf/zjfIv2++67L/3617/Od/GK5w8//HD6/ve/X3UuTz31VJ77jBkz8p28fvnLX+ZuoHj+pz/9KY0YMaK8d1DMpW6HT8lVV12VDj/88DxeY4010kknnZTWX3/99Pbbb6eLLroozynstdde6Y9//GPN73Ecj+uGmEeEaCussEJ65pln0llnnVVeVhfXi8/c3KJDqRTCxYbWbmEPAPPO8jEAoDV+/m7RUChCnliu1VQNTeXEE09M5557bs1zlllmmTR69Oj0gx/8oGaNCKj69++fXn755arHI1S64YYb0sCBAxuc55133pn23Xffql1LpUBozJgxqVevXg3WOfXUU9MZZ5xR8zPFXP/85z+XO6WqiTBqt912S2PHjq16vGPHjulXv/pVOu2001JLEAoBwLcnFAIAWuPn7za/fKzk7LPPTuPHj0/77bdf6tmzZ958OZZgxTKvCFaiA6ihQChESBOdPhEuRddQLKnq1q1b7j76xS9+kZ5++ulGA6EwaNCgfG68JwKgqBG1ombUjms0FgiF008/PXdT7b333vl/2FjmFuHW9ttvn7uDIlhqKBAK0dkU50WYFe+L90edqBd1o35LBUIAAABA+9WinULM/3QKAcC3p1MIAGhMoTuFAAAAAGg+QiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABdWrtCQAAUCw9h49JRTL5nAGtPQUAqEooBAC0OUULDQAAWoPlYwAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACigFg2F3nvvvXTXXXelU045Je20005p6aWXTh06dMhfBx544LeqPX369LT66quX6/Xs2bPJ7/vNb36T+vTpk5Zccsm08MILp7XXXjsde+yx6Y033mjy9ePceE+8N2pErah53nnn5Ws01cMPP5z23XfftOqqq6auXbum5ZZbLu24447pxhtvTPMizt9hhx3y+6NO1Iu6jzzyyDzVAQAAAIqhw5w5c+a0WPEOHWoeO+CAA9KoUaO+ce3jjjsuXXDBBeXnEYJMnjy5wfe88sorqX///unll1+uenyxxRZLN9xwQxo4cGCDde68884cuEybNq3q8d69e6cxY8akXr16NVjntNNOS2eccUaaPXt21eMDBgxIt956aw55apkxY0babbfd0tixY6se79ixYw7lTj311NQSpkyZklZeeeU8fuutt9JKK63UItcBoFh6Dh/T2lOAZjP5nAGtPQUA5gNTWuDn7+9s+dgqq6ySO1maw1NPPZUuuuiiHJYsuuiiTXrPp59+mkOWUiB0yCGHpHvvvTd36px11llpkUUWySHPnnvumf7xj380eO04J86N98R7o0bUipph0qRJ+VpxzVquuuqqdPrpp+dAaI011kgjR45MEyZMSKNHj05bb711PieCpZ/85CcNfq44XgqE4n3x/qgT9aJu1I/wacSIEU36PgEAAADF0Kkli0eHSiypiq9ll102d/Ksttpq36rm119/ncOXeIzulwg/GgpfSmJZV4Q1IZaPHX/88eVjP/jBD9JWW22V+vXrl5d+HXPMMen++++vWufoo4/O3TmdOnVKf/vb3/J7S7bZZpu05pprpmHDhuVrRSdTBDJ1ffjhh+mEE04oh2WPPvpoXlpXEp1KQ4YMyR1JsSzs0EMPzfOr6+9//3v605/+lMeDBg1Kt99+e1pggQXy8/ie77zzzmmjjTZKb775Zr7e7rvvnrp3797o9woAAACY/7Vop1B0wkTAEYFQc7n44ovTE088kdZaa61ysNKYr776Kl1yySV5vM466+S9gOrabLPN0sEHH5zHDzzwQHr88cfrnRMdOA899FAex7mVgVBJ1I5rlOYa167rmmuuSZ988kken3vuuXMFQiGCnSuuuKIc8ESgVc3555+fHyOgqjy/JOpG/fDxxx/n6wIAAAC0u7uPxebO0X0Ufvvb36bOnTs36X333XdfOYSJvYxin51qKje/jq6bumJpVslBBx1UtUbU3n///ctBTFy7Vp3Yw2jXXXetWifWBm633XZ5HEvT6nZDxfN4PcR5tdYSRv24Tq3PBAAAABRTuwqFjjjiiPT555+n/fbbr+pyqlrGjRtXHscSsVo23njj1K1btzweP358zTpxt7FYllVL5TXq1pk5c2buOArRadRQsFWq8+WXX6aJEyfOdSw6maJWY58p6m+66abl91TrXAIAAACKp92EQrF3TmyoHHviVN51rCmef/758jhuIV9LLMMq3THshRdeqHe89FqcE+fWUnmNunVir6HYD6mxuTRWp6mfqfL4rFmzat55raHdzRv6euedd+apHgAAAFCAjaaby0cffZQ3fw7nnHNO6tGjxzy9P8KLUofPEkss0eC5cXu3p59+Or3//vu5Q6dLly759S+++CJNnTo1jxu77VsEV3Gt6GqK28RVm0tT6pRuNReas866667b4Pm13gsAAADMP9pFp1DcKexf//pXXm5Vuu37vCjtxxO3kG9MhDkln332Wb0a81qnssa81qk1l+asAwAAABRTm+8UevDBB9O1116bl2vF5tIdOnSY5xrR5ROasjF1qTMoxK3n69aY1zqVNea1Tq25NGedxtTtUKorlo/17dt3nmoCAAAAra9Nh0KxfOvQQw9Nc+bMSUcffXTaYIMNvlGdrl275sfSxsyNXbNkoYUWqldjXutU1pjXOrXm0px1GtPY0jQAAACgfWrTy8fOOuus9NJLL+V9bU4//fRvXGfRRRdt8tKp2Aeo2rKsUo15rVN3ade81Kk1l+asAwAAABRTm+4UOvfcc/Pjdtttl+68884GA494jDuUhWWWWSZts802c3W7PPbYY/mcjz/+uMHNpkvLpWIz68plV9GZs9RSS6UPPvhgrk2ea22MXZpX3Y2aKztvGqtTuXSrsTobb7zxN6oDAAAAFFObDoVKy6Kuu+66/NWQuDPYXnvtlcf9+vWbKxSKu239+c9/zuMXX3wxbbrpplVrxC3bX3311TxeZ5116h2POg899FB65ZVX8rm1bksf1yipW6d3795pgQUWyLelrzxvXutU3kGsqXVivmuuuWaD5wIAAADF0KaXjzWXLbbYojx+4IEHap43ceLEcofP5ptvXrNOnPPEE0/UrFN5jbp1YlPo0sbMjzzySIP7AZXqRMdS3U6gPn36lDeYbugzRf1HH320/J4FF1yw5rkAAABAcbTpUCg2mG7sa9VVV83nxmPptfvvv3+uOltttVVafPHF8/h3v/tdPqeaUaNGlcdDhgypd3zw4MHlca3OpdmzZ6frr78+j2OZ2tZbb12zzrRp09Jtt91WtU4sCbvnnnvyeNttt51rD6EQz+P1EOfVWooW9eM6tT4TAAAAUExtOhRqLtFR8/Of/zyPX3jhhXT++efXOye6dkaOHFlefhZdNXVFh8+WW26Zx3FuvKeuCy64IF8jxB3TqnXmDB06tBxSDR8+PO9TVCmWlh1xxBH5MRx//PFVP9dxxx2XH2Mp25FHHlk+v3JJ3QknnFAOqOK6AAAAAC2+p9C4cePy/juVIUVJvF7ZmRMOPPDAFptLBCs33XRTmjRpUho2bFi+/o9//ON8i/b77rsv/frXv87hSjy/6KKLata5+OKL85KwGTNmpB122CH98pe/zN1A8Tw2uh4xYkR576Bjjz22ao0ll1wyb6J9+OGHpzfeeCNtsskm6aSTTkrrr79+evvtt/P1Y04h9kmKTqdqYt+k+Axx3b/85S9p++23T8ccc0xaYYUV0jPPPJPv3vbmm2/mc+N63bt3b4bvJAAAADA/6DCn1lqqZhAhTyzXaqpvMpWePXvmYCWWj02ePLnBcyMI6t+/f3r55ZerHl9sscXSDTfckAYOHNhgnbgT2r777ltellVXBEJjxoxJvXr1arDOqaeems4444yanzvmGhtkx53Paokwarfddktjx46terxjx47pV7/6VTrttNNSS4hla6U7msVdzirvigYA31TP4WNaewrQbCafM6C1pwDAfGBKC/z8XYjlYyUR0jz11FO5ayY2bo4lVd26dUtrrbVW+sUvfpGefvrpRgOhMGjQoHxuvCcCoKgRtaJm1I5rNBYIhdNPPz13U+299975f9hY5rbMMsvkjp8//vGPOVhqKBAK0dkU50WYFe+L90edqBd1o35LBUIAAABA+9WinULM/3QKAdASdAoxP9EpBEBz0CkEAAAAQLMQCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFFCLhkLvvfdeuuuuu9Ipp5ySdtppp7T00kunDh065K8DDzywSTWmT5+ebrvttvTTn/409enTJ3Xv3j0tuOCCaamllko/+MEP0mmnnZbefffdJs8p6v3mN7/JtZZccsm08MILp7XXXjsde+yx6Y033mhynTg33hPvjRpRK2qed955+RpN9fDDD6d99903rbrqqqlr165pueWWSzvuuGO68cYb07yI83fYYYf8/qgT9aLuI488Mk91AAAAgGLoMGfOnDktVrxDh5rHDjjggDRq1KgG3//000+nzTffPH322WcNnrfYYoulESNGpD333LPB81555ZXUv3//9PLLL9esc8MNN6SBAwc2WOfOO+/Mgcu0adOqHu/du3caM2ZM6tWrV4N1ItA644wz0uzZs6seHzBgQLr11ltzyFPLjBkz0m677ZbGjh1b9XjHjh1zKHfqqaemljBlypS08sor5/Fbb72VVlpppRa5DgDF0nP4mNaeAjSbyecMaO0pADAfmNICP39/Z8vHVlllldzJMi8idCkFQhEOnX322el//ud/0pNPPpnuvvvudNhhh+XQI87bZ5990l//+teatT799NMcspQCoUMOOSTde++9uVPnrLPOSossskiuE8HSP/7xj5p1nnrqqXxOnBvvifdGjagVNcOkSZPyteKatVx11VXp9NNPz4HQGmuskUaOHJkmTJiQRo8enbbeeut8TgRLP/nJTxr8HsXxUiAU74v3R52oF3WjfoRPEZoBAAAAfCedQtGdEkuq4mvZZZdNkydPTquttlqTO4UibLn44otznXXXXbfqOXfccUcaMmRIio8RIUiEPtU6lKJbJrpyQiwfO/744+tdq1+/fmnWrFn58f777696vR/+8IfpoYceSp06dUoPPvhgXsJWKZaPDRs2rPz5I5Cp68MPP0yrr756+uSTT3JY9sQTT+SldSVff/11/kzRkRTuu+++tNVWW9Wr8/e//z1tu+22eTxo0KB0++23pwUWWKB8fOrUqWmjjTZKb775ZlpiiSXSa6+9lpffNSedQgC0BJ1CzE90CgFQyE6h6ISJpVgRCH0Tm222WbrppptqBkJhl112Sbvuumsev/rqq7mTp66vvvoqXXLJJXm8zjrr5L2Aql3r4IMPzuMHHnggPf744/XOiQ6cCIRCnFs3EApRO64RItCKa9d1zTXX5EAonHvuuXMFQiGCnSuuuKIc8ETQVM3555+fHyOgqjy/JOpG/fDxxx/n6wIAAADMN3cfKy23KgVDdUWnTSmEiQ6lWHJWTeXm19F1U1cszSo56KCDqtaI2vvvv385iIlr16oTexiVAq26IvHbbrvt8jiWptVdihbP4/UQ59VKCKN+XKfWZwIAAACKab4Ihb788svyuG63TBg3blx5HEvDatl4441Tt27d8nj8+PE168TdxmJZVi2V16hbZ+bMmbnjKESnUefOnRutE59v4sSJcx2LTqao1dhnivqbbrpp+T3VOpcAAACA4pkvQqFY7lVSWrpV6fnnny+P4xbytcQyrNIdw1544YV6x0uvxTlxbi2V16hbJzahjj2DGptLY3Wa+pkqj8d+SbXuvAYAAAAUS+1ko5343//933yXrrD++utXDYViM6ZSh09suNyQ2LTp6aefTu+//37u0OnSpUt+/YsvvsgbN4fGNnOKzZzjWp9//nne/KnaXJpSp7SBVGjOOg3t0VRX5XWqeeedd5pcCwAAAGg72nUoFKHN0KFDy503cXv4akr78cQt5BsTYU7JZ599Vg6FKvf0aWqdCIWiRrW5NKVO3bm0RJ3GVAZKAAAAwPyjXS8fO+qoo8p77cQG0nFb9mqiyyc0tH9PSSkECjNmzKhXY17rVNaY1zq15tKcdQAAAIBiaredQmeffXb5Fut9+vRJl19+ec1zu3btmh9LGzM3ddPqhRZaqF6Nea1TWWNe69SaS3PWaUzdZWvVlo/17dt3nmoCAAAAra9dhkJXXXVV+uUvf1neRHns2LFzLZGqa9FFF23y0qlY8lVtWVapxrzWqbu0a17q1JpLc9ZpTGP7FQEAAADtU7tbPnbjjTemI444Io9XXXXV9D//8z9p6aWXblKwEeHIxx9/3KTOmB49esy17Co6c5Zaaqkmbb780UcflYOYunvyVIYsjdWp7NJpqToAAABAMbWrUOgvf/lL2n///dPs2bPT8ssvn+69994mdbJU3m3rxRdfrHle3LL91VdfzeNqdzEr1XnllVfyubVUXqNund69e6cFFlig0bk0Vqepn6nyeKdOndKaa67Z4LkAAABAMbSbUCgCoD322COHMdGxEx1Ca6yxRpPeu8UWW5THDzzwQM3zYtPqUofP5ptvXrNOnPPEE0/UrFN5jbp1YlPo0h48jzzySIP7AZXqRMfSxhtvPNex2EeptMF0Q58p6j/66KPl9yy44II1zwUAAACKo12EQg8//HDaZZdd8obJiy++eLr77rvTeuut1+T3b7XVVvl94Xe/+12aM2dO1fNGjRpVHg8ZMqTe8cGDB5fH1113XdUa0cV0/fXX5/ESSyyRtt5665p1pk2blm677baqdWJJ2D333JPH22677Vx7CIV4Hq+HOK/WErKoH9ep9ZkAAACAYmrzodA//vGPNGDAgNydE5tJjxkzJm200UbzVCM6an7+85/n8QsvvJDOP//8eudE187IkSPzuF+/frmrpq7o8Nlyyy3zOM6N99R1wQUX5GuEo48+umpnztChQ8sh1fDhw9MHH3ww1/Gvv/4675sUj+H444+v+rmOO+64/BjdU0ceeWT5/JKpU6emE044oRxQxXUBAAAAWvzuY+PGjcv771SGFCXxemVnTjjwwAPneh77++y4447lzaHPPPPMHKY8++yzNa+5zDLL5K+6Ili56aab0qRJk9KwYcPy9X/84x/nW7Tfd9996de//nUOV+L5RRddVLP+xRdfnJeEzZgxI+2www75LmjRDRTP//SnP6URI0aU9w469thjq9ZYcskl07nnnpsOP/zw9MYbb6RNNtkknXTSSWn99ddPb7/9dr5+zCnstddeudOpmm222SZ/hrhu7Le0/fbbp2OOOSatsMIK6ZlnnklnnXVWevPNN/O5cb3u3bvX/FwAAABAsXSYU2stVTOIkCeWazVV3alEaHTQQQfN0zVPPfXUdNppp1U9FkFQ//7908svv1z1+GKLLZZuuOGGNHDgwAavceedd6Z99923vCyrrgiEoqOpV69ejc71jDPOqLmcLeb65z//Od/5rJYIo3bbbbc0duzYqsc7duyYfvWrX9X8nnxbsWytdEezuMuZW9gD0Bx6Dh/T2lOAZjP5nAGtPQUA5gNTWuDn7za/fKw5RUjz1FNP5a6Z2Lg5llR169YtrbXWWukXv/hFevrppxsNhMKgQYPyufGeCICiRtSKmlE7rtFYIBROP/303E2199575/9hY5lbdDlFx88f//jHHCw1FAiF6GyK8yLMivfF+6NO1Iu6Ub+lAiEAAACg/WrRTiHmfzqFAGgJOoWYn+gUAqA56BQCAAAAoFkIhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABdSptScAADSu5/AxrT0FAADmMzqFAAAAAApIpxAAALSgonX6TT5nQGtPAYAm0ikEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQC0aCr333nvprrvuSqecckraaaed0tJLL506dOiQvw488MB5rvfXv/41DRkyJK200kqpS5cu+TGex+tNNWvWrPTb3/42bbnllqlHjx5poYUWSmussUY67LDD0nPPPdfkOlOnTs2fa4MNNkiLLbZY/opxvPbBBx80uc6zzz6brx1ziLnEnGJuMceY63f5vQEAAACKo8OcOXPmtFjxDh1qHjvggAPSqFGjmlRn9uzZ6dBDD00jR46sec7QoUPTVVddlTp27NhgkNO/f//0+OOPVz0eYcpll12WazXkscceS4MHD07vvvtu1ePLL798Gj16dOrbt2+Dda6++up01FFHpZkzZ1Y9Hu8fM2ZMDtNa+nvzTU2ZMiWtvPLKefzWW2/lMAqA5tdz+JjWngJAk0w+Z0BrTwFgvjSlBX7+/s6Wj62yyipphx12+EbvPemkk8qhx4YbbphuvPHGNGHChPwYz8M111yTTj755Jo1vv7669w5UwqEdt1119xFEwHPJZdckpZZZpn05Zdf5q6dhrpr4hs/aNCgHAh16tQpDRs2LD344IP5K8bx2jvvvJPPif/Bahk7dmw6/PDDcyC07LLL5jnEXOLaMbcQnzHmHHNvye8NAAAAUDwt2il06qmnpj59+uSvCD4mT56cVltttXnqFJo0aVJab7318lKqjTfeOIcvscyqZPr06alfv35p4sSJOZB54YUXUq9everVufbaa9PBBx+cx0cccUS6/PLL5zr+yiuvpI022ihNmzYtvz/qRL269t9///T73/8+j2+++ea0++67z3U8Xttzzz0b/IxfffVVWnvttdNrr72Wl509+eSTeflYpSOPPDJdccUVeXzddddVXW7XXN+bb0OnEMB3Q6cQ0F7oFAJoGe2uU+j0009PAwcOzIHQN3XRRReV99a59NJL5wo9Qrdu3fLrIc678MILq9Y5//zz8+OSSy6ZzjvvvHrHIyw58cQTywHR7bffXu+c6A664YYb8njHHXesFwiFPfbYIx8LER5VW2IWtSMQCnHNuoFQiDl27969PG7J7w0AAABQPG367mPRxHTHHXfkcXTWbLrpplXPi9fXWmutPI7z6zY/RUdNdMmUQpsIS6qp7MapFgr95S9/yXv4hIMOOqjmvEt14tx4T12x31C1a1aKOcZcw/PPP58/Q0t8bwAAAIBiatOh0Ouvv57efvvtPI5lUA0pHf/nP/+Zl6lVGjduXL3zqlluueVS796983j8+PH1jje1TuWxhupEWBPX/CZ1mut7AwAAABRT/U1z2pDokCmJbpiGVB6PrqDS3kXfpE505cT6vM8//zwtvPDC9eosvvjiDYY5cfex2Cso9icqdSiVfPbZZ7n2N/lMLfG9aUxDm2WH2FQbAAAAaH/adChUGUg0toFSabOlUApdvk2dWGYV7ystvaqs05TNnKLOc8891yxzCS1VpzGV7wUAAADmH216+dinn35aHi+yyCINnlvZ0RPdOC1Zp7EalXVaei7ftg4AAABQTG26U+iLL74ojzt37tzguV26dCmPZ8yY0aJ1GqtRWael5/Jt6zSmsc6iWD7Wt2/feaoJAAAAtL42HQp17dq1PJ45c2aD53755Zflcd1bs9etU/l8XutMnz690blU1mlsLk2p0ZJ1GtOUpXIAAABA+9Oml48tuuiiTV72FJtC11pO1dx1mrIEq1SnpefybesAAAAAxdSmQ6HKLpXG7oJVucyp7ubI36ROhw4d6nXJlJ43VqOyTt25rLjiivM8l2p1mut7AwAAABRTmw6F1l133fL4xRdfbPDcyuPrrLPOt64T4UnlBs2VdT755JP07rvvNrjPTtyOvtpcosOnFMx815+pWh0AAACgmNp0KLTaaqulFVZYIY8feOCBBs998MEHy504PXv2nOvYFltsUR43VCeCnkmTJuXx5ptvXu94U+tUHmuozksvvdRguNRQneb63gAAAADF1KZDoVjCtcsuu5S7XR599NGq58XrpW6YOD/eV6l3797lDpmbb745bxZdzahRo8rjIUOG1Du+8847p44d/+9bdt1119Wcd6lOnBvvqWvw4MFVr1kp5hhzLXUFxWdoie8NAAAAUExtOhQKxxxzTFpggQXy+Gc/+1m9W6rH83g9dOrUKZ9fzXHHHZcfP/zwwzRs2LB6x1999dV09tln53GvXr2qhkLLLbdc2mefffL47rvvTrfeemu9c2655ZZ8LOy33375PXVF7dVXXz2P45px7bqOP/749NFHH5XHLfm9AQAAAIqnRW9JP27cuPTKK6+Un0+dOrU8jtfrdskceOCB9WpEh0yEIuecc06aOHFiXkZ1wgknpDXWWCOHKeeee2566qmn8rlx3pprrll1LgcccEC69tpr0/jx49Pll1+el20dcsghqXv37mnChAnpjDPOyPsARXfPJZdckkOUas4666z03//93+n9999Pe+21V57TwIED87G77rorXXDBBXnco0ePdOaZZ1atseCCC6ZLL700DRo0KF8zPtPJJ5+c+vbtm4Ogq6++Ov35z38uLzWLcKma5vreAAAAAMXTYc6cOXNaqniEPL/73e+afH6tqcyePTsHOBHq1HLwwQenESNGlJd3VROhVP/+/dPjjz9e9XiXLl3SZZddloYOHdrgPB977LG8BKzWfkDRHTR69Oi0ySabNFgnwp+jjjoqzZw5s+rxCInGjBmTll566Zo1mut7803Fnc9KG2fHXc7q3rENgObRc/iY1p4CQJNMPmdAa08BYL40pQV+/m7zy8dChBkjR47MAUnsixMbLHfu3Dk/xvOxY8ema665ptHQI8KVhx9+OF1xxRW5A2eppZZKXbt2zUu5Ilh54oknGg2EQoQ9zzzzTO7u+d73vpcWWWSR/LX++uvn15599tlGA6FQumY8xhxiLjGnmNuVV16Zu5oaCoSa83sDAAAAFEuLdgox/9MpBPDd0CkEtBc6hQBaRmE7hQAAAABoXkIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAI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2nrrrXMX0fHHH5/+93//t/z+RRddtMnLpyo3ha67LKu56zRlKVepTkvNBQAAACimNh8KRchz7bXX5nFsLF13L6GSTp065T2FwuzZs9OoUaOqdrtUbtDc2HKpupssf5M6HTp0qNdtU3reWI3KOnXnsuKKK87zXKrVAQAAAIqpzYdCsVfQhx9+mMcbbrhhg+dutNFG5fGLL75YHq+77rpVX6+m8vg666wz17FvUidCmMqNnivrfPLJJ+ndd99tcL+eadOmVZ1LdAqVAp5v85kAAACAYmrzoVB0AJXMmjWrwXO/+uqrqu+LzadXWGGFPH7ggQcarPHggw+WO3F69uw517EtttiiPG6oTgQ9kyZNyuPNN9+83vGm1qk81lCdl156qcFwqbE6AAAAQPG0+Y2ml1xyybxvUHTMPPLIIzkYqgx8aoUfEQSVxBKuXXbZJV155ZW5a+bRRx9Nm266ab33x+ulrpo4P95XKZavRafNCy+8kG6++eZ0wQUXpG7dutWrU7l0bciQIfWOx93RfvrTn+Zlbtddd13ac889q36eUp2OHTvWu6NaiE21b7zxxvK5w4cPr3dObGgdcy11KMVnAGjveg4f09pTAACAdq/NdwpFIDJgwIA8fvvtt9NZZ51V9byPPvoonXDCCeXnAwcOnOv4Mccck+9cFn72s5/VuzV7PI/XQ4ROcX41xx13XH6MJW1xF7S6Xn311XT22Wfnca9evaqGQsstt1zaZ5998vjuu+9Ot956a71zbrnllnws7Lfffvk9dUXtuANbiGvGteuKTbfje1MaAwAAAIQOc2In5zYuundiv6DoegmDBg3KG06XbkkfHT4XXXRRevPNN/PxbbfdNt1zzz316px44onpnHPOKe9PFCHSGmuskcOUuKPZU089VT7v17/+ddW5fP3116lfv35p/Pjx+fmPfvSjdMghh6Tu3bunCRMm5M2u33vvvRxm3XXXXWmnnXaquflzfKb3338/h1DHHntsOciK90UXUnRF9ejRIz355JM1bw0/duzY/P2IrqNll102nXzyyalv3745CLr66qvTn//85/JSs/vvv78cjDWX2OS6tLdRfCa3sAe+CzqFANquyef83y90AUht/ufvdhEKhQh59tprrzR16tQGz9tmm21y502ENHVFcBIBTuluZtUcfPDBacSIETnUqSXm0L9///T4449XPd6lS5d02WWXpaFDhzY418ceeywvAau1H1B0B40ePTptsskmDdaJ8Oeoo45KM2fOrHo8QqIxY8akpZdeOjU3oRDQGoRCALQVQjDgu1LoUCh88MEHaeTIkemvf/1reu6559LHH3+cu2wiPOnTp0/ae++98947dfcCqtZdE8FPhDoR8ERYEu8/7LDDanb21BVdPBHG/PGPf8x7DH3++ed5M+voUjr66KPTeuut16Q6cf2LL744hz+TJ08u74cUexrFEralllqqSXWeffbZdMkll6R77703L7OLO57F/kexTC3CqVr7MH1bQiGgNQiFAGgrhELAd6XwoRBtj1AIaA1CIQDaCqEQ0J5//m7zG00DAAAA0PyEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABdQuQ6E333wznXrqqWnjjTdOPXr0SF27dk0rr7xy2nLLLdMpp5ySnn322Qbf/9e//jUNGTIkrbTSSqlLly75MZ7H6001a9as9Nvf/jZfM+aw0EILpTXWWCMddthh6bnnnmtynalTp+Y5b7DBBmmxxRbLXzGO1z744IMm14nPHNeOOcRcYk4xt5hjzBUAAACgUoc5c+bMSe3IpZdemk488cT0+eef1zzn6KOPThdddFG912fPnp0OPfTQNHLkyJrvHTp0aLrqqqtSx44dGwxy+vfvnx5//PGqxyNouuyyy3Kthjz22GNp8ODB6d133616fPnll0+jR49Offv2bbDO1VdfnY466qg0c+bMqsfj/WPGjElLL710am5TpkzJgVx46623csAG0NJ6Dh/T2lMAgGzyOQNaewpAQUxpgZ+/21Wn0Jlnnpl+/vOf50Cod+/e6bzzzkv3339/euqpp9I999yTn2+22WY1A52TTjqpHAhtuOGG6cYbb0wTJkzIj/E8XHPNNenkk0+uOYevv/46dxWVAqFdd901dxhFwHPJJZekZZZZJn355Ze5a6ehzqP4H3DQoEE5EOrUqVMaNmxYevDBB/NXjOO1d955J58T/8PXMnbs2HT44YfnQGjZZZfNc4i5xLVjbiE+Y8w55g4AAADQrjqF7r333rTddtvl8f7775/DmwUXXLDquRGQdO7cea7XJk2alNZbb728lCqWnUX4EsusSqZPn5769euXJk6cmAOZF154IfXq1ate7WuvvTYdfPDBeXzEEUekyy+/fK7jr7zyStpoo43StGnT8vujTtSrKz7D73//+zy++eab0+677z7X8Xhtzz33zOMDDjggjRo1ql6Nr776Kq299trptddey8vOnnzyybx8rNKRRx6Zrrjiijy+7rrr0oEHHpiak04hoDXoFAKgrdApBHxXCtspFMu+fvrTn+bxv/3bv+Vun1qBUKgbCIVYTlbaWyeWoFUGQqFbt2759RDnXXjhhVVrn3/++flxySWXzJ1JdUUQFMvbSgHR7bffXu+c6A664YYb8njHHXesFwiFPfbYIx8LER5VW2IWtSMQCnHNuoFQiDl27969PAYAAABoN6HQ3/72t/Tyyy/n8QknnFC186Yh0Qx1xx135HF01my66aZVz4vX11prrTyO8+s2UUW3UXT+lEKbCJKqqezGqRYK/eUvf8lBVzjooINqzrtUJ86N99QV+w1Vu2almGPMNTz//PP5MwAAAAC0i1DolltuyY8dOnRIAwcOLL/+4Ycf5rAoHhvy+uuvp7fffjuPY4lYQ0rH//nPf6bJkyfPdWzcuHH1zqtmueWWy3sehfHjx9c73tQ6lccaqhNBVlzzm9YBAAAAiqddhEKPPvpofuzZs2dadNFF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bYNOIQDgu6BTCFqPTqGU0ptvvpl+9atf5XHsmdO5c+cmhUilZWaXXnrpXIFQ6NatW349xHkXXnhh1Trnn39+flxyySXTeeedV+94BEEnnnhiOSC6/fbb650T3UE33HBDHu+44471AqGwxx575GMhwqNqS8yidgRCIa5ZNxAKMcfu3buXxwAAAADtNhSK7pfPPvssd9BEd09johHqjjvuyOPorNl0002rnhevr7XWWnkc59dtoIpuo+j8KYU2ESRVU9mNUy0U+stf/pKXv4WDDjqo5rxLdeLceE9dsd9QtWtWijnGXMPzzz+fPwMAAABAuwuFYlnVXXfdlTt1Sl07jXn99dfT22+/nceNhUil4//85z/T5MmT5zo2bty4eudVs9xyy6XevXvn8fjx4+sdb2qdymMN1YkgK675TesAAAAAxVR/w5s26uOPP05HH310Hp977rlNvptWdMiURKdQQyqPR1fQaqut9o3rRFdOrPH7/PPP08ILL1yvzuKLL95gmBN3H4u9gmJ/olKHUkl0SkXtb/KZ5lVDG12H2BAbAAAAaH/aTSgUd+WKvXU233zz8mbP8xpqNLYJU2nDplAKXb5NnViCFu8rLUurrNOUDaGiznPPPdcscwl16zRF5fsBAACA+Ue7WD720EMP5dvFx528YnPpDh06NPm9n376aXm8yCKLNHhuZUdPdOO0ZJ3GalTWaam5AAAAAMXV5juFZs6cmQ499NDcdRO3of/e9743T+//4osvyuPG7lTWpUuX8njGjBktWqcpd00r1WmpuTRFY91FsXysb9++81wXAAAAaF1tPhT69a9/nV588cW0yiqrpFNPPXWe39+1a9e5AqaGfPnll+Vx3dvW161T+Xxe60yfPr3RuVTWaWwuTalRrU5TNGWZGwAAAND+tOnlYxEGnX322Xl86aWXzrUUqqkWXXTRJi+fik2hay3Lau46TVnKVarTUnMBAAAAiqtNdwpdeOGFuRNm9dVXz901f/rTn+qd8+yzz5bHf//73/Nm1GHQoEE5RKrsdGnsTlqVS6XqbrBct05Ddz8r1Ym9j+p22sTzf/3rX43OpbJO3bmsuOKKc82lKTWq1QEAAACKq02HQqWlT6+99lraa6+9Gj3/jDPOKI9ff/31HAqtu+66c3UeNaTy+DrrrDPXsbp1vv/97zdaJ0KYut1NUeeJJ55In3zySQ6wat2WPvbqidvRV5tLdApF7Qh8vs1nAgAAAIqrTS8faw6rrbZaWmGFFfL4gQceaPDcBx98sNyJ07Nnz7mObbHFFuVxQ3Ui6Jk0aVIeb7755vWON7VO5bGG6rz00kvl7qhvUgcAAAAopjYdCo0aNSrfdayhr8rNp++7777y66VQJ5Zw7bLLLuWumUcffbTqteL1UldNnF/3tve9e/cud9rcfPPNeTlbrTmXDBkypN7xnXfeOXXs+H/f9uuuu67Bzx7i3HhPXYMHD656zUoxx5hrqUMpPgMAAABAmw+FmssxxxyTFlhggTz+2c9+Vu/W7PE8Xg+dOnXK51dz3HHH5ccPP/wwDRs2rN7xV199tbwxdq9evaqGQrFcbJ999snju+++O9166631zrnlllvysbDffvtVXWIWtWOvpRDXjGvXdfzxx6ePPvqoPAYAAAAoVCgUHTKlUGTixIl5GdVNN92Ux/EYz2Mc4rw111yzap0DDjigvATr8ssvT7vttlsObyZMmJAuu+yytNlmm+V9gKK755JLLskBUzVnnXVW6tGjRx7HXknDhw9P48aNy18x3nvvvfOxOOfMM8+sWmPBBRfMd2SLa8U1Y14xh5hLzCnmdsUVV5SXmkW4BAAAAFDSYU6stWrHTjvttHT66aeXl49ttdVWVc+bPXt2OuSQQ9K1115bs9bBBx+cRowYUV7eVc3UqVNT//790+OPP171eJcuXXI4M3To0Abn/dhjj+UlYLX2A4ruoNGjR6dNNtmkwTpXX311Ouqoo/Jd2qrp27dvGjNmTIN3S/s24u5npbuaxcbXde+2Bnw3eg4f09pTAAAKYPI5A1p7ClBYU1rg5+9CdAqFCHpGjhyZA5LYMyg2n+7cuXN+jOdjx45N11xzTYOBUIhw5eGHH85dONGBs9RSS6WuXbvmpVwROsWdxRoLhEKEPc8880w6+eST0/e+9720yCKL5K/1118/v/bss882GgiF0jXjMeYQc4k5xdyuvPLKNH78+BYLhAAAAID2q913CtG6dApB26BTCAD4LugUgtajUwgAAACAZiEUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAEJhQAAAAAKSCgEAAAAUEBCIQAAAIACEgoBAAAAFJBQCAAAAKCAhEIAAAAABSQUAgAAACggoRAAAABAAQmFAAAAAApIKAQAAABQQEIhAAAAgAISCgEAAAAUkFAIAAAAoICEQgAAAAAFJBQCAAAAKCChEAAAAEABCYUAAAAACkgoBAAAAFBAQiEAAACAAhIKAQAAABSQUAgAAACggIRCAAAAAAUkFAIAAAAoIKEQAAAAQAF1Su3AxIkT09ixY9O4cePS888/n95///204IILphVWWCFtvvnm6eCDD05bbLFFk+v99a9/TSNGjEiPP/54rtWjR4/Up0+fdOihh6addtqpSTVmzZqVrrnmmnTDDTekF198MX322Wd5Ptttt136+c9/ntZbb70m1Zk6dWq65JJL0ujRo9PkyZPzaz179kyDBw9ORx99dFpqqaWaVOfZZ59Nl156abrnnnvS22+/nRZZZJG09tprp3322ScNHTo0derULv6nhmbTc/iY1p4CAMB8p2j/xpp8zoDWngK0qA5z5syZk9qwH/7wh+mhhx5q9Lz9998/XX311alz5841z5k9e3YOfkaOHFnznAhQrrrqqtSxY8cGg5z+/fvnUKmaLl26pMsuuyzXashjjz2Ww59333236vHll18+h0V9+/ZtsE587qOOOirNnDmz6vF4/5gxY9LSSy+dmtuUKVPSyiuvnMdvvfVWWmmllZr9GvBNFO0fLAAAND+hEG1JS/z83eaXj0XXS4gunOicufXWW9OECRPSI488kv7rv/4rrbjiivn49ddfnw488MAGa5100knlQGjDDTdMN954Y64Vj/E8RPfPySefXLPG119/nYYMGVIOhHbdddfceRQBT3T8LLPMMunLL79Mhx12WH69lvgfcNCgQTkQii6eYcOGpQcffDB/xThee+edd/I58T98LdFBdfjhh+dAaNlll81ziLnEtWNuIT5jzDnmDgAAANAuOoUGDhyYu4B+9KMfpQUWWKBq104sIZs0aVJ+/sADD+TuorrieCzpimVfG2+8cQ5fFlpoofLx6dOnp379+uWlahHIvPDCC6lXr1716lx77bV5uVo44ogj0uWXXz7X8VdeeSVttNFGadq0afn9Uafa0q34TL///e/z+Oabb0677777XMfjtT333DOPDzjggDRq1Kh6Nb766qu8ROy1115Liy22WHryySfTGmusMdc5Rx55ZLriiivy+Lrrrms0OJtXOoVoq3QKAQDwbekUoi0pZKfQXXfdlfbYY4+qgVCIJVEXXHBB+Xl0ElVz0UUX5UAoxN47lYFQ6NatW349xHkXXnhh1Trnn39+flxyySXTeeedV+94BEEnnnhiOSC6/fbb650T3UGxF1HYcccd6wVCIT5zHAsRHlVbYha1IxAKcc26gVCIOXbv3r08BgAAAGgXoVBTbL311uXxq6++Wu94NEPdcccdeRydNZtuumnVOvH6Wmutlcdxft0mqug2is6fUmgTQVI1ld041UKhv/zlL3l/o3DQQQfV/FylOnFuvKeu2G+o2jUrxRxjriE26S51VAEAAADFNl+EQrGHT0m1jqLXX3+9vDdRLBFrSOn4P//5z/LdwEri7md1z6tmueWWS717987j8ePH1zve1DqVxxqqE0FWXPOb1gEAAACKZ764T3nsI1Syzjrr1DseHTIl0SnUkMrj0RW02mqrfeM60ZUT6/w+//zztPDCC9ers/jiizcY5sTdx2KvoNifqNShVPLZZ5/l2t/kM82Lhja5DrEZNgAAAND+tPtQKJZWnXPOOeXnpaVStYKNxjZiKm3aFEqhy7epE0vQ4n2lZWmVdZqyKVTUee6555plLqFunaZcHwAAAJj/tPvlY7EhdNxyPcQt2OPOX3V9+umn5fEiiyzSYL3Kjp7oxmnJOo3VqKzTUnMBAAAAiqlTe182Nnz48DxeZpll0pVXXln1vC+++KI87ty5c4M1u3TpUh7PmDGjRes0VqOyTkvNpTGNdRbF8rG+ffvOU00AAACg9bXbUCiWVA0ZMiTfPr5r167plltuycFQNXG8ZObMmU3etLrubevr1ql8Pq91pk+f3uhcKus0Npem1KhWpzFNWeIGAAAAtD/tcvlY3E1shx12SB999FG+29if/vSn9MMf/rDm+YsuumiTl0/FptC1lmU1d52mLOUq1WmpuQAAAADF1O5Cobi1/HbbbZcfO3TokK699tq0yy67NLnbpbG7aVUul6q7yfI3qRNzrNttU3reWI3KOnXnsuKKK87zXKrVAQAAAIqpXYVCU6dOTdtvv3167bXX8vNLL7007b///o2+b9111y2PX3zxxQbPrTxe9/b236ROhDCVGz1X1vnkk0/Su+++2+B+PXE7+mpziU6hUsDzbT4TAAAAUEztJhSKAGXHHXdMzz//fH4et6E/8sgjm/Te1VZbLa2wwgrlzakb8uCDD5Y7cXr27DnXsS222KI8bqhOBD2TJk3K480337ze8abWqTzWUJ2XXnqpwXCpsToAAABA8bSLUCg2ZR4wYEB68skn8/OTTjopnXDCCU1+fyzhKi0xi66ZRx99tOp58XqpqybOj/dV6t27d7nT5uabb87zqmbUqFHlcWyGXdfOO++cOnb8v2/9ddddV3PepTpxbrynrsGDB1e9ZqWYY8y11KEUnwEAAACgzYdCcWetCFbGjx+fnx999NHpzDPPnOc6xxxzTN6UOvzsZz+rd2v2eB6vh06dOuXzqznuuOPy44cffpiGDRtW7/irr76azj777Dzu1atX1VBoueWWS/vss08e33333enWW2+td07cTS2Ohf322y+/p66ovfrqq+dxXDOuXdfxxx+fN+QujQEAAABChzlz5sxpy9+KH/3oR+m2227L42222SZddNFF9Tp4KnXu3LlmN8yJJ56Yl52FDTfcMHcbrbHGGjlMOffcc9NTTz1VPu/Xv/511Rpff/116tevXzmkivkdcsghqXv37mnChAnpjDPOSO+9917u7rnrrrvSTjvtVHPz54022ii9//77OYQ69thj08CBA/OxeN8FF1yQZs2alXr06JE7pGrdGn7s2LFp0KBBafbs2WnZZZdNJ598curbt28Ogq6++ur05z//ubzU7P777y8HY80lNrku7W0Un8kt7Gkreg4f09pTAACgnZt8zoDWngK06M/fbT4UaigAqmbVVVdNkydPrnosgpMIcOKOZbUcfPDBacSIEeXlXbU2vO7fv396/PHHqx7v0qVLuuyyy9LQoUMbnOtjjz2Wl4DV2g8ouoNGjx6dNtlkkwbrRPhz1FFH5a6qaiIkGjNmTFp66aVT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0kunBRdcMHXv3j1ttNFG6dhjj00vvfRSa0+Tdmjy5Mnp0ksvTT/60Y/Smmuumbp165a6du2aVlpppTR48OD0pz/9Kc2aNau1p0k789lnn6UHH3wwnX/++WmPPfZIq622WurQoUP+6tmzZ2tPjzbqjTfeyH+frb322mnhhRdOSy65ZOrTp08677zz0vTp01t7erQj7733XrrrrrvSKaecknbaaaf876bSf4MOPPDA1p4e7djEiRPTf/7nf6Yddtgh/1upS5cuaZFFFkm9e/dOBx10UBo3blxrT5F2Ztq0afnf2/H3X79+/VKvXr3S4osvnjp37pyWWWaZtNVWW6Xf/OY36YMPPkhF02HOnDlzWnsS0BbED+QRADUmzom/pIYPH/6dzIv271e/+lU666yzUmP/uY0fym699da0yiqrfGdzo33beuut0/3331/12KqrrprDSKh05513pn333Tf/47ia+IErfkES/1iGxkT4U8sBBxyQRo0a9Z3Oh/nDD3/4w/TQQw81et7++++frr766vxDPTTmnnvuSdtvv32j5y299NLpD3/4Q9pxxx1TUXRq7QlAWxJpcaTEm2yySVp99dXT8ssvnzs63n777fyD17XXXps++eSTdOKJJ6YlllgiHX744a09ZdqBd955JwdC8Rv5IUOGpG233TZ3C0Wn0AsvvJAuueSS9Pjjj+ev7bbbLj355JP5t2HQmMqgMbo9Nt544/Twww/nDiKo66mnnkp77rlnmjFjRv5vTPxdFsFiPI/fnsYPV5MmTcrdsvFb+kUXXbS1p0w7Er/QiO6zv/3tb609Fdq5+Hd3WGGFFdLuu++ettxyy/zn6+uvv06PPPJIuuCCC9I///nPdP3116evvvoq/fGPf2ztKdNOrLzyyvnvvVgBEuP4WW/27NlpypQp+Rezt912W5o6dWraeeed04QJE9K//du/pSLQKQQV4i+bBRZYoObx119/Pf9H5KOPPko9evTIP+w3dD6EE044IS211FLppz/9adUfsuLP3d57751uvvnm/Pz000/PrfjQmBEjRuQ/U9FlVursiGVjsTxIpxC1fvveqVOnvOzwBz/4wVzHY/nYsGHD8vjUU0/Ny6mhIfHnJP77E1/LLrts/m9OLGMNOoX4pgYOHJi7gGLJfbV/Z8cP7ZtvvnkOscMDDzyQ//sG3+bnvDB69Oj8C9wQ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", "text/plain": [ "
" ] }, "metadata": { "image/png": { "height": 413, "width": 578 } }, "output_type": "display_data" } ], "source": [ "h = bh.Histogram(uproot_hist)\n", "plt.bar(h.axes[0].centers, h.values(), width=h.axes[0].widths);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could use a `Weight()` storage and read both `allvalues` and `allvariances` in, as well, since ROOT histograms can sometimes have this enabled." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's clean up after ourselves:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "if demo_file.is_file():\n", " demo_file.unlink()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/notebooks/xarray.ipynb000066400000000000000000006265511516627434300237220ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# XArray Example\n", "\n", "To run this example, an environment.yml similar to this one could be used:\n", "\n", "```yaml\n", "name: bh_xhistogram\n", "channels:\n", " - conda-forge\n", "dependencies:\n", " - python==3.9\n", " - boost-histogram\n", " - xhistogram\n", " - matplotlib\n", " - netcdf4\n", "```" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import xarray as xr\n", "from xhistogram.xarray import histogram as xhistogram\n", "\n", "import boost_histogram as bh" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at using boost-histogram to imitate the xhistogram package by reading and producing xarrays. As a reminder, xarray is a sort of generalized Pandas library, supporting ND labeled and indexed data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simple 1D example\n", "\n", "We will start with the first example from the xhistogram docs:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "da = xr.DataArray(np.random.randn(100, 30), dims=[\"time\", \"x\"], name=\"foo\")\n", "bins = np.linspace(-4, 4, 20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### xhistogram\n", "\n", "And, this is what historamming and plotting looks like:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
<xarray.DataArray 'histogram_foo' (foo_bin: 19)>\n",
       "array([  2,   1,   2,  22,  56, 135, 234, 375, 453, 496, 456, 346, 226,\n",
       "        96,  65,  25,   7,   3,   0])\n",
       "Coordinates:\n",
       "  * foo_bin  (foo_bin) float64 -3.789 -3.368 -2.947 -2.526 ... 2.947 3.368 3.789
" ], "text/plain": [ "\n", "array([ 2, 1, 2, 22, 56, 135, 234, 375, 453, 496, 456, 346, 226,\n", " 96, 65, 25, 7, 3, 0])\n", "Coordinates:\n", " * foo_bin (foo_bin) float64 -3.789 -3.368 -2.947 -2.526 ... 2.947 3.368 3.789" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "h = xhistogram(da, bins=[bins])\n", "display(h)\n", "h.plot()\n", "# h is an xarray" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### boost-histogram (direct usage)\n", "\n", "Let's first just try this by hand, to see how it works. This will not return an xarray, etc." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "bh_bins = bh.axis.Regular(19, -4, 4)\n", "bh_hist = bh.Histogram(bh_bins).fill(np.asarray(da).flatten())\n", "plt.plot(bh_hist.axes[0].centers, bh_hist.values());" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### boost-histogram (adapter function)\n", "\n", "Now, let's make an adaptor for boost-histogram." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def bh_xhistogram(*args, bins):\n", " # Convert bins to boost-histogram axes first\n", " prepare_bins = (bh.axis.Variable(b) for b in bins)\n", " h = bh.Histogram(*prepare_bins)\n", "\n", " # We need flat NP arrays for filling\n", " prepare_fill = (np.asarray(a).flatten() for a in args)\n", " h.fill(*prepare_fill)\n", "\n", " # Now compute the xarray output.\n", " return xr.DataArray(\n", " h.values(),\n", " name=\"_\".join(a.name for a in args) + \"_histogram\",\n", " coords=[\n", " (f\"{a.name}_bin\", arr.flatten(), a.attrs)\n", " for a, arr in zip(args, h.axes.centers, strict=False)\n", " ],\n", " )" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
<xarray.DataArray 'foo_histogram' (foo_bin: 19)>\n",
       "array([  2.,   1.,   2.,  22.,  56., 135., 234., 375., 453., 496., 456.,\n",
       "       346., 226.,  96.,  65.,  25.,   7.,   3.,   0.])\n",
       "Coordinates:\n",
       "  * foo_bin  (foo_bin) float64 -3.789 -3.368 -2.947 -2.526 ... 2.947 3.368 3.789
" ], "text/plain": [ "\n", "array([ 2., 1., 2., 22., 56., 135., 234., 375., 453., 496., 456.,\n", " 346., 226., 96., 65., 25., 7., 3., 0.])\n", "Coordinates:\n", " * foo_bin (foo_bin) float64 -3.789 -3.368 -2.947 -2.526 ... 2.947 3.368 3.789" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "h = bh_xhistogram(da, bins=[bins])\n", "display(h)\n", "h.plot();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### More features\n", "\n", "Let's add a few more features to our function defined above.\n", "* Let's allow bins to be a list of axes or even a completely prepared histogram; this will allow us to take advantage of boost-histogram features later.\n", "* Let's add a weights keyword so we can do weighted histograms as well." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def bh_xhistogram(*args, bins, weights=None):\n", " \"\"\"\n", " bins is either a histogram, a list of axes, or a list of bins\n", " \"\"\"\n", "\n", " if isinstance(bins, bh.Histogram):\n", " h = bins\n", " else:\n", " prepare_bins = (\n", " b if isinstance(b, bh.axis.Axis) else bh.axis.Variable(b) for b in bins\n", " )\n", " h = bh.Histogram(*prepare_bins)\n", "\n", " prepare_fill = (np.asarray(a).flatten() for a in args)\n", "\n", " if weights is None:\n", " h.fill(*prepare_fill)\n", " else:\n", " prepared_weights, *_ = xr.broadcast(weights, *args)\n", " h.fill(*prepare_fill, weight=np.asarray(prepared_weights).flatten())\n", "\n", " return xr.DataArray(\n", " h.values(),\n", " name=\"_\".join(a.name for a in args) + \"_histogram\",\n", " coords=[\n", " (f\"{a.name}_bin\", arr.flatten(), a.attrs)\n", " for a, arr in zip(args, h.axes.centers, strict=False)\n", " ],\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2D example\n", "\n", "This also comes from the xhistogram docs." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "# Read WOA using opendap\n", "Temp_url = (\n", " \"http://apdrc.soest.hawaii.edu:80/dods/public_data/WOA/WOA13/5_deg/annual/temp\"\n", ")\n", "Salt_url = (\n", " \"http://apdrc.soest.hawaii.edu:80/dods/public_data/WOA/WOA13/5_deg/annual/salt\"\n", ")\n", "Oxy_url = (\n", " \"http://apdrc.soest.hawaii.edu:80/dods/public_data/WOA/WOA13/5_deg/annual/doxy\"\n", ")\n", "\n", "ds = xr.merge(\n", " [\n", " xr.open_dataset(Temp_url).tmn.load(),\n", " xr.open_dataset(Salt_url).smn.load(),\n", " xr.open_dataset(Oxy_url).omn.load(),\n", " ]\n", ")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "sbins = np.arange(31, 38, 0.025)\n", "tbins = np.arange(-2, 32, 0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### xhistogram" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/Caskroom/miniconda/base/envs/xtest/lib/python3.8/site-packages/xarray/core/computation.py:601: RuntimeWarning: divide by zero encountered in log10\n", " result_data = func(*input_data)\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "hTS = xhistogram(ds.smn, ds.tmn, bins=[sbins, tbins])\n", "np.log10(hTS.T).plot(levels=31)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### boost-histogram\n", "\n", "We could hand in the same bin definitions, but let's use boost-histogram axes instead:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "sax = bh.axis.Regular(250, 31, 38)\n", "tax = bh.axis.Regular(340, -2, 32)\n", "\n", "hTS = bh_xhistogram(ds.smn, ds.tmn, bins=[sax, tax])\n", "np.log10(hTS.T).plot(levels=31)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Speed comparson" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "17.6 ms ± 508 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit\n", "hTS = xhistogram(ds.smn, ds.tmn, bins=[sbins, tbins])" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4.7 ms ± 245 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%%timeit\n", "hTS = bh_xhistogram(ds.smn, ds.tmn, bins=[sax, tax])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Weighted histogram" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try a more complex example from the docs; the dVol weights one:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "dz = np.diff(ds.lev)\n", "dz = np.insert(dz, 0, dz[0])\n", "dz = xr.DataArray(dz, coords={\"lev\": ds.lev}, dims=\"lev\")\n", "\n", "dVol = dz * (5 * 110e3) * (5 * 110e3 * np.cos(ds.lat * np.pi / 180))\n", "\n", "hTSw = bh_xhistogram(ds.smn, ds.tmn, bins=[sax, tax], weights=dVol)\n", "np.log10(hTSw.T).plot(levels=31, vmin=11.5, vmax=16, cmap=\"brg\")" ] } ], "metadata": { "kernelspec": { "display_name": "boost-hist", "language": "python", "name": "boost-hist" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.10" } }, "nbformat": 4, "nbformat_minor": 4 } scikit-hep-boost-histogram-5f6f3e3/noxfile.py000066400000000000000000000100541516627434300213450ustar00rootroot00000000000000# /// script # dependencies = ["nox>=2025.2.9"] # /// from __future__ import annotations import argparse import shutil from pathlib import Path import nox nox.needs_version = ">=2025.2.9" nox.options.default_venv_backend = "uv|virtualenv" DIR = Path(__file__).parent.resolve() @nox.session def tests(session: nox.Session) -> None: """ Run the unit and regular tests. """ opts = ( ["--reinstall-package=boost-histogram"] if session.venv_backend == "uv" else [] ) args = session.posargs or ["-n", "auto", "--benchmark-disable"] pyproject = nox.project.load_toml("pyproject.toml") session.install(*nox.project.dependency_groups(pyproject, "test")) session.install("-v", "-e.", *opts, silent=False) session.run("pytest", *args) @nox.session(default=False) def hist(session: nox.Session) -> None: """ Run Hist's test suite """ session.install(".") tmpdir = session.create_tmp() session.chdir(tmpdir) shutil.rmtree("hist") session.run("git", "clone", "https://github.com/scikit-hep/hist", external=True) session.chdir("hist") session.install(".", "--group=test", "--group=plot", "mypy", "pandas-stubs") session.run("pytest", *session.posargs) session.run("mypy") @nox.session(reuse_venv=True, default=False) def docs(session: nox.Session) -> None: """ Build the docs. Pass --non-interactive to avoid serving. Pass "-b linkcheck" to check links. """ pyproject = nox.project.load_toml("pyproject.toml") parser = argparse.ArgumentParser() parser.add_argument( "-b", dest="builder", default="html", help="Build target (default: html)" ) args, posargs = parser.parse_known_args(session.posargs) serve = args.builder == "html" and session.interactive extra_installs = ["sphinx-autobuild"] if serve else [] session.install(*nox.project.dependency_groups(pyproject, "docs"), *extra_installs) shared_args = ( "-n", # nitpicky mode "-T", # full tracebacks f"-b={args.builder}", "docs", *(posargs or [f"docs/_build/{args.builder}"]), ) if serve: session.run( "sphinx-autobuild", "--open-browser", "--ignore=docs/.build", *shared_args ) else: session.run("sphinx-build", "--keep-going", *shared_args) @nox.session(default=False) def build_api_docs(session: nox.Session) -> None: """ Build (regenerate) API docs. """ pyproject = nox.project.load_toml("pyproject.toml") session.install(*nox.project.dependency_groups(pyproject, "docs")) session.run( "sphinx-apidoc", "-o", "docs/api/", "--no-toc", "--template", "docs/template/", "--force", "--module-first", "src/boost_histogram", ) @nox.session def lint(session: nox.Session) -> None: """ Run the linter. """ session.install("prek") session.run("prek", "run", "--all-files", *session.posargs) @nox.session def pylint(session: nox.Session) -> None: """ Run pylint. """ session.install("pylint==4.0.*") session.install(".") session.run("pylint", "boost_histogram", *session.posargs) @nox.session(default=False) def make_pickle(session: nox.Session) -> None: """ Make a pickle file for this version """ pyproject = nox.project.load_toml("pyproject.toml") session.install(".", *nox.project.dependency_groups(pyproject, "dev")) session.run("python", "tests/pickles/make_pickle.py", *session.posargs) @nox.session(default=False, venv_backend=None) def bump_boost(session: nox.Session) -> None: """ Bump boost. Requires a version number. """ parser = argparse.ArgumentParser() parser.add_argument("version", help="Version to checkout") args = parser.parse_args(session.posargs) extern = DIR / "extern" for path in extern.iterdir(): session.chdir(path) session.run("git", "fetch", external=True) session.run("git", "switch", "--detach", f"boost-{args.version}", external=True) if __name__ == "__main__": nox.main() scikit-hep-boost-histogram-5f6f3e3/pyproject.toml000066400000000000000000000211471516627434300222500ustar00rootroot00000000000000[build-system] requires = ["scikit-build-core>=0.11", "pybind11>=3"] build-backend = "scikit_build_core.build" [project] name = "boost-histogram" dynamic = ["version"] description = "The Boost::Histogram Python wrapper." readme = "README.md" requires-python = ">=3.10" authors = [ { name = "Hans Dembinski", email = "hans.dembinski@gmail.com" }, { name = "Henry Schreiner", email = "hschrein@cern.ch" }, ] keywords = [ "boost-histogram", "histogram", ] license = "BSD-3-Clause AND BSL-1.0" license-files = [ "LICENSE", "extern/*/LICENSE" ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: C++", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Free Threading :: 3 - Stable", "Programming Language :: Python :: Free Threading", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python", "Topic :: Scientific/Engineering :: Information Analysis", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Scientific/Engineering :: Physics", "Topic :: Scientific/Engineering", "Topic :: Software Development", "Topic :: Utilities", "Typing :: Typed", ] dependencies = [ "numpy>=1.21.3", ] [project.urls] "Bug Tracker" = "https://github.com/scikit-hep/boost-histogram/issues" Changelog = "https://boost-histogram.readthedocs.io/en/latest/CHANGELOG.html" Chat = " https://gitter.im/HSF/PyHEP-histogramming" Discussions = "https://github.com/scikit-hep/boost-histogram/discussions" Documentation = "https://boost-histogram.readthedocs.io/" Homepage = "https://github.com/scikit-hep/boost-histogram" [dependency-groups] dev = [ { include-group = "test" }, "ipykernel", "nbconvert", "setuptools_scm", "typer", "uhi", ] docs = [ "ipython", "myst_parser>=0.13", "nbsphinx", "numpy>=1.21.3", "sphinx-book-theme>=0.0.33", "sphinx>=4.0", "sphinx_copybutton", ] examples = [ "matplotlib", "netCDF4", "numba", "uproot", "xarray", "xhistogram", ] test = [ "cloudpickle", "pytest-benchmark", "pytest>=6.0", "pytest-benchmark", "numpy>=1.21.3", "hypothesis>=6.0; sys_platform != 'ios'", # slow on iOS, can fail health check "pytest-xdist; sys_platform != 'ios'", # No processes on iOS ] github = [ { include-group = "test" }, "setuptools_scm", "pytest-github-actions-annotate-failures" ] [tool.scikit-build] minimum-version = "build-system.requires" metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" sdist.exclude = [ "extern/**/*.py", "extern/**/*.md", "extern/**/*.rst", "extern/**/*.toml", "extern/*/checks/**", "extern/*/test/**", "extern/*/benchmark/**", "extern/*/doc/**", "extern/*/examples/**", "extern/*/tools/**", "extern/pybind11/docs/**", "extern/pybind11/tests/**", "extern/*/README.md", ] logging.level = "INFO" [[tool.scikit-build.generate]] path = "boost_histogram/version.py" template = ''' __version__ = version = "$version" ''' [tool.setuptools_scm] [tool.uv.pip] only-binary = ["numpy"] [tool.pytest.ini_options] minversion = "6.0" junit_family = "xunit2" addopts = [ "-ra", "--strict-markers", "--strict-config", "--import-mode=importlib", ] filterwarnings = [ "error", "default:could not create cache path:pytest.PytestCacheWarning", ] xfail_strict = true testpaths = ["tests"] required_plugins = ["pytest-benchmark"] log_level = "DEBUG" [tool.mypy] python_version = "3.10" files = ["src", "examples"] strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false disallow_untyped_calls = false [[tool.mypy.overrides]] module = ["matplotlib.*", "typer"] ignore_missing_imports = true [tool.cibuildwheel] build-frontend = "build[uv]" test-groups = ["test"] test-command = "python -m pytest -n auto --benchmark-disable tests" test-sources = ["pyproject.toml", "tests"] test-environment.CI = "1" # Hypothosis needs this on GraalPy test-skip = [ "cp310-win_arm64", "cp310-musllinux_*", "cp31*-musllinux_*", # Threading test crashes "gp311_242-macosx_x86_64", "gp311_242-manylinux_aarch64", "gp311_242-win*", # pytest crashes with module 'io' has no attribute '_WindowsConsoleIO' "gp312_*", # No numpy wheels yet "*-ios_*", # Fails on CI with no device found ] enable = ["pypy", "graalpy"] environment-pass = ["SETUPTOOLS_SCM_PRETEND_VERSION"] environment.PIP_ONLY_BINARY = "numpy" environment.PIP_PREFER_BINARY = "1" environment.UV_ONLY_BINARY = "numpy" environment.UV_PREFER_BINARY = "1" [tool.cibuildwheel.pyodide] build-frontend = {name = "build", args = ["--exports", "whole_archive"]} test-command = "pytest --benchmark-disable tests" [tool.cibuildwheel.ios] build-frontend = "build" xbuild-tools = ["cmake", "ninja"] test-command = "python -m pytest --benchmark-disable tests" environment.PIP_EXTRA_INDEX_URL = "https://pypi.anaconda.org/beeware/simple/" # [tool.cibuildwheel.android] # build-frontend = "build" # xbuild-tools = ["cmake", "ninja"] # test-command = "pytest --benchmark-disable tests" # environment.PIP_EXTRA_INDEX_URL = "https://chaquo.com/pypi-13.1" [[tool.cibuildwheel.overrides]] select = ["gp*"] inherit.environment = "append" environment.UV_INDEX = "https://www.graalvm.org/python/wheels/" environment.UV_INDEX_STRATEGY = "unsafe-best-match" test-command = "pytest --benchmark-disable tests" [tool.pylint] py-version = "3.10" ignore-patterns = ['.*\.pyi'] ignore = "version.py" extension-pkg-allow-list = ["boost_histogram._core"] reports.output-format = "colorized" similarities.ignore-imports = "yes" messages_control.disable = [ "cyclic-import", # TODO: move files out of _internal "fixme", "invalid-name", "line-too-long", "missing-class-docstring", "missing-function-docstring", "missing-module-docstring", "c-extension-no-member", # C extensions mess with this "protected-access", "too-few-public-methods", "too-many-arguments", "too-many-branches", "too-many-lines", "too-many-locals", "too-many-return-statements", "too-many-statements", "too-many-positional-arguments", "wrong-import-position", "unused-argument", # Covered by ruff "unsubscriptable-object", # Wrongly triggered ] [tool.ruff.lint] extend-select = [ "ARG", # flake8-unused-arguments "B", # flake8-bugbear "C4", # flake8-comprehensions "EXE", # flake8-executable "FURB", # refurb "G", # flake8-logging-format "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat "PD", # pandas-vet "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # pylint "PT", # flake8-pytest-style "PTH", # flake8-use-pathlib "PYI", # flake8-pyi "TID251", # flake8-tidy-imports.banned-api "RET", # flake8-return "RUF", # Ruff-specific "SIM", # flake8-simplify "T20", # flake8-print "UP", # pyupgrade "YTT", # flake8-2020 ] ignore = [ "PLR09", # Too many X "PLR2004", # Magic value in comparison "PT013", # from pytest import approx is fine "PT011", # pytest.raises too broad "PYI034", # We are returning Self, just generic ] typing-modules = ["boost_histogram._compat.typing"] isort.required-imports = ["from __future__ import annotations"] exclude = ["extern/*"] [tool.ruff.lint.flake8-tidy-imports.banned-api] "typing.Self".msg = "Use boost_histogram._compat.typing.Self instead." "typing_extensions.Self".msg = "Use boost_histogram._compat.typing.Self instead." [tool.ruff.lint.mccabe] max-complexity = 13 [tool.ruff.lint.per-file-ignores] "docs/*" = ["T20"] "examples/*" = ["T20"] "scripts/*" = ["T20"] "tests/*" = ["T20"] "notebooks/*" = ["T20"] "*.pyi" = ["F401", "TID251"] "docs/conf.py" = ["ARG001"] "src/boost_histogram/test.py" = ["PT"] "**.ipynb" = ["I002", "E731"] "src/boost_histogram/_compat/**.py" = ["TID251"] scikit-hep-boost-histogram-5f6f3e3/scripts/000077500000000000000000000000001516627434300210165ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/scripts/performance_report.py000077500000000000000000000110531516627434300252670ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import annotations import math import multiprocessing from timeit import timeit print("Welcome to boost-histogram's performance report") setup_1d = """ import numpy as np import boost_histogram as bh bins=100 ranges=(-1,1) bins = np.asarray(bins).astype(np.int64) ranges = np.asarray(ranges).astype(np.float64) edges = np.linspace(*ranges, bins+1) np.random.seed(42) vals = np.random.normal(size=[50_000_000]).astype(np.float32) """ setup_2d = """ import numpy as np import boost_histogram as bh bins=(100, 100) ranges=((-1,1),(-1,1)) bins = np.asarray(bins).astype(np.int64) ranges = np.asarray(ranges).astype(np.float64) edges = (np.linspace(*ranges[0,:], bins[0]+1), np.linspace(*ranges[1,:], bins[1]+1)) np.random.seed(42) vals = np.random.normal(size=[2, 5_000_000]).astype(np.float64) """ def timer(setup, statement, n=10): t = timeit(statement, setup, number=n) return (t * 1000) / n def print_timer(setup, statement, name, storage, fill, flow, base=None, n=10): time = timer( setup, statement.format(name=name, storage=storage, fill=fill, flow=flow), n ) speedup = 1 if base is None else base / time print( table_line.format( name=name, storage=storage, fill=fill, flow=str(flow), time=time, speedup=speedup, ) ) return time c = multiprocessing.cpu_count() counts = [c // 2**x for x in reversed(range(int(math.log2(c) + 1)))] table_header = "| Type | Storage | Threads | Flow | Time | Speedup |" table_spacer = "|-------|---------------|----------|------|-----------|---------|" table_line = ( "|{name:^7}|{storage:^15}|{fill:^9}|{flow:<6}|{time:>7.4g} ms |{speedup:>7.2g}x |" ) print() print("### 1D, 10 runs each") print() print(table_header) print(table_spacer) base = print_timer( setup_1d, "h, _ = np.histogram(vals, bins=bins, range=ranges)", name="NumPy", storage="uint64", fill="", flow=False, ) print_timer( setup_1d, "hist = bh.Histogram(bh.axis.{name}(bins, *ranges, underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(vals)", name="Regular", storage="Int64", fill="", flow=False, base=base, ) print_timer( setup_1d, "hist = bh.Histogram(bh.axis.{name}(bins, *ranges, underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(vals)", name="Regular", storage="Int64", fill="", flow=True, base=base, ) for fill in counts: print_timer( setup_1d, "hist = bh.Histogram(bh.axis.{name}(bins, *ranges, underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(vals, threads={fill})", name="Regular", storage="Int64", fill=fill, flow=True, base=base, ) for fill in counts: print_timer( setup_1d, "hist = bh.Histogram(bh.axis.{name}(bins, *ranges, underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(vals, threads={fill})", name="Regular", storage="AtomicInt64", fill=fill, flow=True, base=base, ) print() print("### 2D, 10 runs each") print() print(table_header) print(table_spacer) base = print_timer( setup_2d, "H, *ledges = np.histogram2d(*vals, bins=bins, range=ranges)", name="NumPy", storage="uint64", fill="", flow=False, ) print_timer( setup_2d, "hist = bh.Histogram(bh.axis.{name}(bins[1], *ranges[0], underflow={flow}, overflow={flow}), bh.axis.{name}(bins[1], *ranges[1], underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(*vals)", name="Regular", storage="Int64", fill="", flow=False, base=base, ) for fill in counts: print_timer( setup_2d, "hist = bh.Histogram(bh.axis.{name}(bins[1], *ranges[0], underflow={flow}, overflow={flow}), bh.axis.{name}(bins[1], *ranges[1], underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(*vals, threads={fill})", name="Regular", storage="Int64", fill=fill, flow=False, base=base, ) for fill in counts: print_timer( setup_2d, "hist = bh.Histogram(bh.axis.{name}(bins[1], *ranges[0], underflow={flow}, overflow={flow}), bh.axis.{name}(bins[1], *ranges[1], underflow={flow}, overflow={flow}), storage=bh.storage.{storage}()); hist.fill(*vals, threads={fill})", name="Regular", storage="AtomicInt64", fill=fill, flow=False, base=base, ) scikit-hep-boost-histogram-5f6f3e3/src/000077500000000000000000000000001516627434300201165ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/000077500000000000000000000000001516627434300233215ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/__init__.py000066400000000000000000000011231516627434300254270ustar00rootroot00000000000000from __future__ import annotations from . import accumulators, axis, numpy, storage from .histogram import Histogram, IndexingExpr, Kind from .tag import ( # pylint: disable=redefined-builtin loc, overflow, rebin, sum, underflow, ) # pylint: disable-next=import-error from .version import version as __version__ __all__ = [ "Histogram", "IndexingExpr", "Kind", "__version__", "accumulators", "axis", "loc", "numpy", "overflow", "rebin", "storage", "sum", "underflow", ] def __dir__() -> list[str]: return __all__ scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_compat/000077500000000000000000000000001516627434300247435ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_compat/__init__.py000066400000000000000000000000001516627434300270420ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_compat/typing.py000066400000000000000000000004311516627434300266250ustar00rootroot00000000000000from __future__ import annotations import sys import typing if sys.version_info >= (3, 11): from typing import Self elif typing.TYPE_CHECKING: from typing_extensions import Self else: Self = object __all__ = ["Self"] def __dir__() -> list[str]: return __all__ scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/000077500000000000000000000000001516627434300244105ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/__init__.pyi000066400000000000000000000000731516627434300266720ustar00rootroot00000000000000from . import accumulators, algorithm, axis, hist, storage scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/accumulators.pyi000066400000000000000000000067751516627434300276540ustar00rootroot00000000000000from typing import Any, overload from numpy.typing import ArrayLike from typing_extensions import Self class _BaseAccumulator: def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... def __imul__(self, other: float) -> Self: ... def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... def _ipython_key_completions_(self) -> tuple[str, ...]: ... class WeightedSum(_BaseAccumulator): @overload def __init__(self) -> None: ... @overload def __init__(self, value: float) -> None: ... @overload def __init__(self, value: float, variance: float) -> None: ... @property def value(self) -> float: ... @property def variance(self) -> float: ... def __iadd__(self, arg0: float) -> Self: ... def fill(self, value: ArrayLike, variance: ArrayLike | None = None) -> Self: ... @staticmethod def _make(a: ArrayLike, b: ArrayLike) -> WeightedSum: ... @staticmethod def _array(a: ArrayLike, b: ArrayLike) -> WeightedSum: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... class Sum(_BaseAccumulator): @overload def __init__(self) -> None: ... @overload def __init__(self, value: float) -> None: ... @property def value(self) -> float: ... def __iadd__(self, arg0: float) -> Self: ... def fill(self, value: ArrayLike) -> Self: ... @property def _small(self) -> float: ... @property def _large(self) -> float: ... class WeightedMean(_BaseAccumulator): @overload def __init__(self) -> None: ... @overload def __init__( self, sum_of_weights: float, sum_of_weights_squared: float, value: float, variance: float, ) -> None: ... @property def sum_of_weights(self) -> float: ... @property def sum_of_weights_squared(self) -> float: ... @property def value(self) -> float: ... @property def _sum_of_weighted_deltas_squared(self) -> float: ... @property def variance(self) -> float: ... def __call__( self, value: ArrayLike, *, weight: ArrayLike | None = None ) -> Self: ... def fill(self, value: ArrayLike, *, weight: ArrayLike | None = None) -> Self: ... @staticmethod def _make( arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike ) -> WeightedMean: ... @staticmethod def _array( arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike ) -> WeightedMean: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... class Mean(_BaseAccumulator): @overload def __init__(self) -> None: ... @overload def __init__(self, count: float, value: float, variance: float) -> None: ... @property def count(self) -> float: ... @property def value(self) -> float: ... @property def _sum_of_deltas_squared(self) -> float: ... @property def variance(self) -> float: ... def __call__( self, value: ArrayLike, *, weight: ArrayLike | None = None ) -> Self: ... def fill(self, value: ArrayLike, *, weight: ArrayLike | None = None) -> Self: ... @staticmethod def _make(arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> Mean: ... @staticmethod def _array(arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> Mean: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/algorithm.pyi000066400000000000000000000027271516627434300271310ustar00rootroot00000000000000import enum import typing class reduce_command: iaxis: int class slice_mode(enum.Enum): shrink = enum.auto() crop = enum.auto() @typing.overload def shrink_and_rebin( iaxis: int, lower: float, upper: float, merge: int ) -> reduce_command: ... @typing.overload def shrink_and_rebin(lower: float, upper: float, merge: int) -> reduce_command: ... @typing.overload def crop_and_rebin( iaxis: int, lower: float, upper: float, merge: int ) -> reduce_command: ... @typing.overload def crop_and_rebin(lower: float, upper: float, merge: int) -> reduce_command: ... @typing.overload def slice_and_rebin( iaxis: int, begin: int, end: int, merge: int, mode: slice_mode = ... ) -> reduce_command: ... @typing.overload def slice_and_rebin( begin: int, end: int, merge: int, mode: slice_mode = ... ) -> reduce_command: ... @typing.overload def rebin(iaxis: int, merge: int) -> reduce_command: ... @typing.overload def rebin(merge: int) -> reduce_command: ... @typing.overload def shrink(iaxis: int, lower: int, upper: int) -> reduce_command: ... @typing.overload def shrink(lower: int, upper: int) -> reduce_command: ... @typing.overload def crop(iaxis: int, lower: float, upper: float) -> reduce_command: ... @typing.overload def crop(lower: float, upper: float) -> reduce_command: ... @typing.overload def slice(iaxis: int, begin: int, end: int, mode: slice_mode) -> reduce_command: ... @typing.overload def slice(begin: int, end: int, mode: slice_mode) -> reduce_command: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/axis/000077500000000000000000000000001516627434300253545ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/axis/__init__.pyi000066400000000000000000000074771516627434300276550ustar00rootroot00000000000000from collections.abc import Iterable, Iterator from typing import Any import numpy as np from numpy.typing import ArrayLike from typing_extensions import Self from . import transform class _BaseAxis: def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... def __imul__(self, other: float) -> Self: ... def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... def _ipython_key_completions_(self) -> tuple[str, ...]: ... @property def traits_underflow(self) -> bool: ... @property def traits_overflow(self) -> bool: ... @property def traits_circular(self) -> bool: ... @property def traits_growth(self) -> bool: ... @property def traits_continuous(self) -> bool: ... @property def traits_ordered(self) -> bool: ... @property def raw_metadata(self) -> Any: ... @raw_metadata.setter def raw_metadata(self, item: Any) -> None: ... @property def size(self) -> int: ... @property def extent(self) -> int: ... @property def edges(self) -> np.typing.NDArray[Any]: ... @property def centers(self) -> np.typing.NDArray[Any]: ... @property def widths(self) -> np.typing.NDArray[Any]: ... def index(self, arg0: ArrayLike) -> int | np.typing.NDArray[Any]: ... def value(self, arg0: ArrayLike) -> float | np.typing.NDArray[Any]: ... class _BaseRegular(_BaseAxis): def __init__(self, bins: int, start: float, stop: float) -> None: ... def __iter__(self) -> Iterator[tuple[float, float]]: ... def bin(self, arg0: int) -> tuple[float, float]: ... class regular_none(_BaseRegular): ... class regular_uflow(_BaseRegular): ... class regular_oflow(_BaseRegular): ... class regular_uoflow(_BaseRegular): ... class regular_uoflow_growth(_BaseRegular): ... class regular_circular(_BaseRegular): ... class regular_pow(_BaseRegular): def __init__(self, bins: int, start: float, stop: float, power: float) -> None: ... @property def transform(self) -> transform.pow: ... class regular_trans(_BaseRegular): def __init__( self, bins: int, start: float, stop: float, transform: transform._BaseTransform ) -> None: ... @property def transform(self) -> transform._BaseTransform: ... class _BaseVariable(_BaseAxis): def __init__(self, edges: Iterable[float]) -> None: ... def __iter__(self) -> Iterator[tuple[float, float]]: ... def bin(self, arg0: int) -> tuple[float, float]: ... class variable_none(_BaseVariable): ... class variable_uflow(_BaseVariable): ... class variable_oflow(_BaseVariable): ... class variable_uoflow(_BaseVariable): ... class variable_uoflow_growth(_BaseVariable): ... class variable_circular(_BaseVariable): ... class _BaseInteger(_BaseAxis): def __init__(self, start: int, stop: int) -> None: ... def __iter__(self) -> Iterator[int]: ... def bin(self, arg0: int) -> int: ... class integer_none(_BaseInteger): ... class integer_uflow(_BaseInteger): ... class integer_oflow(_BaseInteger): ... class integer_uoflow(_BaseInteger): ... class integer_growth(_BaseInteger): ... class integer_circular(_BaseInteger): ... class _BaseCatInt(_BaseAxis): def __init__(self, categories: Iterable[int]) -> None: ... def __iter__(self) -> Iterator[int]: ... def bin(self, arg0: int) -> int: ... class category_int_none(_BaseCatInt): ... class category_int(_BaseCatInt): ... class category_int_growth(_BaseCatInt): ... class _BaseCatStr(_BaseAxis): def __init__(self, categories: Iterable[str]) -> None: ... def __iter__(self) -> Iterator[str]: ... def bin(self, arg0: int) -> str: ... class category_str_none(_BaseCatStr): ... class category_str(_BaseCatStr): ... class category_str_growth(_BaseCatStr): ... class boolean(_BaseAxis): def __iter__(self) -> Iterator[int]: ... def bin(self, arg0: int) -> int: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/axis/transform.pyi000066400000000000000000000016271516627434300301200ustar00rootroot00000000000000from collections.abc import Callable from typing import Any from typing_extensions import Self def _log_fn(arg0: float) -> float: ... def _exp_fn(arg0: float) -> float: ... def _sqrt_fn(arg0: float) -> float: ... def _sq_fn(arg0: float) -> float: ... class _BaseTransform: def forward(self, arg0: float) -> float: ... def inverse(self, arg0: float) -> float: ... def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... class id(_BaseTransform): ... class sqrt(_BaseTransform): ... class log(_BaseTransform): ... class pow(_BaseTransform): def __init__(self, power: float) -> None: ... @property def power(self) -> float: ... class func_transform(_BaseTransform): def __init__( self, forward: Callable[[float], float], inverse: Callable[[float], float], convert: Callable[[float], float], name: str, ) -> None: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/hist.pyi000066400000000000000000000072451516627434300261120ustar00rootroot00000000000000from typing import Any, ClassVar import numpy as np from numpy.typing import ArrayLike from typing_extensions import Self from . import accumulators, axis, storage from .axis import transform _axes_limit: int class _BaseHistogram: _storage_type: ClassVar[type[storage._BaseStorage]] # Note that storage has a default simply because subclasses always handle it. def __init__( self, axis: list[axis._BaseAxis], storage: storage._BaseStorage = ... ) -> None: ... def rank(self) -> int: ... def size(self) -> int: ... def reset(self) -> None: ... def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... def __iadd__(self, other: _BaseHistogram) -> Self: ... def to_numpy(self, flow: bool = ...) -> tuple[np.typing.NDArray[Any], ...]: ... def view(self, flow: bool = ...) -> np.typing.NDArray[Any]: ... def axis(self, i: int = ...) -> axis._BaseAxis: ... def fill( self, *args: ArrayLike, weight: ArrayLike | None = ..., sample: ArrayLike | None = ..., ) -> None: ... def empty(self, flow: bool = ...) -> bool: ... def reduce(self, *args: Any) -> Self: ... def project(self, *args: int) -> Self: ... def sum(self, flow: bool = ...) -> Any: ... def at(self, *args: int) -> Any: ... class any_int64(_BaseHistogram): def __idiv__(self, other: any_int64) -> Self: ... def __imul__(self, other: any_int64) -> Self: ... def at(self, *args: int) -> int: ... def _at_set(self, value: int, *args: int) -> None: ... def sum(self, flow: bool = ...) -> int: ... class any_unlimited(_BaseHistogram): def __idiv__(self, other: any_unlimited) -> Self: ... def __imul__(self, other: any_unlimited) -> Self: ... def at(self, *args: int) -> float: ... def _at_set(self, value: float, *args: int) -> None: ... def sum(self, flow: bool = ...) -> float: ... class any_double(_BaseHistogram): def __idiv__(self, other: any_double) -> Self: ... def __imul__(self, other: any_double) -> Self: ... def at(self, *args: int) -> float: ... def _at_set(self, value: float, *args: int) -> None: ... def sum(self, flow: bool = ...) -> float: ... class any_atomic_int64(_BaseHistogram): def __idiv__(self, other: any_atomic_int64) -> Self: ... def __imul__(self, other: any_atomic_int64) -> Self: ... def at(self, *args: int) -> int: ... def _at_set(self, value: int, *args: int) -> None: ... def sum(self, flow: bool = ...) -> int: ... class any_weight(_BaseHistogram): def __idiv__(self, other: any_weight) -> Self: ... def __imul__(self, other: any_weight) -> Self: ... def at(self, *args: int) -> accumulators.WeightedSum: ... def _at_set(self, value: accumulators.WeightedSum, *args: int) -> None: ... def sum(self, flow: bool = ...) -> accumulators.WeightedSum: ... class any_mean(_BaseHistogram): def at(self, *args: int) -> accumulators.Mean: ... def _at_set(self, value: accumulators.Mean, *args: int) -> None: ... def sum(self, flow: bool = ...) -> accumulators.Mean: ... class any_weighted_mean(_BaseHistogram): def at(self, *args: int) -> accumulators.WeightedMean: ... def _at_set(self, value: accumulators.WeightedMean, *args: int) -> None: ... def sum(self, flow: bool = ...) -> accumulators.WeightedMean: ... class any_multi_cell(_BaseHistogram): def at(self, *args: int) -> float: ... def _at_set(self, value: float, *args: int) -> ArrayLike: ... def sum(self, flow: bool = ...) -> ArrayLike: ... def nelem(self) -> int: ... def reset_nelem(self, nelem: int) -> None: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_core/storage.pyi000066400000000000000000000012251516627434300265770ustar00rootroot00000000000000from typing import Any from typing_extensions import Self class _BaseStorage: def __init__(self) -> None: ... def __eq__(self, other: object) -> bool: ... def __ne__(self, other: object) -> bool: ... def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... class int64(_BaseStorage): ... class double(_BaseStorage): ... class atomic_int64(_BaseStorage): ... class unlimited(_BaseStorage): ... class weight(_BaseStorage): ... class mean(_BaseStorage): ... class weighted_mean(_BaseStorage): ... class multi_cell(_BaseStorage): def __init__(self, nelem: int, /): ... @property def nelem(self) -> int: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/_utils.py000066400000000000000000000125141516627434300251750ustar00rootroot00000000000000from __future__ import annotations import typing from collections.abc import Callable, Iterator from typing import ClassVar, Protocol, TypeVar import boost_histogram class Registerable(Protocol): _types: ClassVar[set[type[object]]] class HasFamily(Protocol): _family: ClassVar[object] T = TypeVar("T") def register( cpp_types: set[type[object]] | None = None, ) -> Callable[[type[T]], type[T]]: """ Decorator to register a C++ type to a Python class. Each class given will be added to a lookup list "_types" that cast knows about. It should also part of a "family", and any class in a family will cast to the same family. You do not need to register a class if it inherits from the C++ class. For example, internally this call: ax = hist._axis(0) which will get a raw C++ object and need to cast it to a Python wrapped object. There is currently one candidate (users could add more): boost_histogram. Cast will use the parent class's family to return the correct family. If the requested family is not found, then the regular family is the fallback. This decorator, like other decorators in boost-histogram, is safe for pickling since it does not replace the original class. If nothing or an empty set is passed, this will ensure that this class is not selected during the cast process. This can be used for simple renamed classes that inject warnings, etc. """ def add_registration(cls: type[T]) -> type[T]: if cpp_types is None or len(cpp_types) == 0: cls._types = set() # type: ignore[attr-defined] return cls if not hasattr(cls, "_types"): cls._types = set() # type: ignore[attr-defined] for cpp_type in cpp_types: if cpp_type in cls._types: # type: ignore[attr-defined] raise TypeError(f"You are trying to register {cpp_type} again") cls._types.add(cpp_type) # type: ignore[attr-defined] return cls return add_registration # TODO: Split into is_class/not is class versions (better typing) def _cast_make_object(canidate_class: T, cpp_object: object, is_class: bool) -> T: "Make an object for cast" if is_class: return canidate_class if hasattr(canidate_class, "_convert_cpp"): return canidate_class._convert_cpp(cpp_object) # type: ignore[no-any-return] # Casting down does not work in pybind11, # see https://github.com/pybind/pybind11/issues/1640 # so for now, all non-copy classes must have a # _convert_cpp method. return canidate_class(cpp_object) # type: ignore[operator, no-any-return] def cast(self: object, cpp_object: object, parent_class: type[T]) -> T: """ This converts a C++ object into a Python object. This takes the parent object, the C++ object, the Python class. If a class is passed in instead of an object, this will return a class instead. The parent object (self) can be either a registered class or an instance of a registered class. Instances simply have their class replaced. If a class does not support direction conversion in the constructor, it should have _convert_cpp class method instead. Example: cast(self, hist.cpp_axis(), Axis) # -> returns Regular(...) if regular axis, etc. If self is None, just use the boost_histogram family. """ if self is None: family: object = boost_histogram else: family = self._family # type: ignore[attr-defined] # Convert objects to classes, and remember if we did so if isinstance(cpp_object, type): is_class = True cpp_class = cpp_object else: is_class = False cpp_class = cpp_object.__class__ # Remember the fallback class if a class in the same family does not exist fallback_class = None for canidate_class in _walk_subclasses(parent_class): # If a class was registered with this c++ type if hasattr(canidate_class, "_types"): ccr = typing.cast(Registerable, canidate_class) is_valid_type = cpp_class in ccr._types else: is_valid_type = cpp_class in set(_walk_bases(canidate_class)) if is_valid_type and hasattr(canidate_class, "_family"): ccf = typing.cast(HasFamily, canidate_class) # Return immediately if the family is right if ccf._family is family: return _cast_make_object(ccf, cpp_object, is_class) # type: ignore[return-value] # Or remember the class if it was from the main family if ccf._family is boost_histogram: fallback_class = ccf # If no perfect match was registered, return the main family if fallback_class is not None: return _cast_make_object(fallback_class, cpp_object, is_class) # type: ignore[return-value] raise TypeError( f"No conversion to {parent_class.__name__} from {cpp_object} found." ) def _walk_bases(cls: type[object]) -> Iterator[type[object]]: for base in cls.__bases__: yield from _walk_bases(base) yield base def _walk_subclasses(cls: type[object]) -> Iterator[type[object]]: for base in cls.__subclasses__(): # Find the furthest child to allow # user subclasses to work yield from _walk_subclasses(base) yield base scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/accumulators.py000066400000000000000000000012731516627434300264000ustar00rootroot00000000000000from __future__ import annotations from ._core.accumulators import ( # pylint: disable=import-error,no-name-in-module Mean, Sum, WeightedMean, WeightedSum, ) from .typing import Accumulator __all__ = ("Accumulator", "Mean", "Sum", "WeightedMean", "WeightedSum") for cls in (Sum, Mean, WeightedSum, WeightedMean): cls.__module__ = "boost_histogram.accumulators" del cls # Not supported by pybind builtins # Enable if wrapper added # inject_signature("self, value")(Sum.fill) # inject_signature("self, value, *, variance=None")(WeightedSum.fill) # inject_signature("self, value, *, weight=None")(Mean.fill) # inject_signature("self, value, *, weight=None")(WeightedMean.fill) scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/axis/000077500000000000000000000000001516627434300242655ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/axis/__init__.py000066400000000000000000000666541516627434300264170ustar00rootroot00000000000000from __future__ import annotations import copy from collections.abc import Callable, Iterable, Iterator from dataclasses import dataclass from functools import partial from typing import ( Any, ClassVar, Literal, TypedDict, TypeVar, ) import numpy as np # pylint: disable=unused-import import boost_histogram from .._compat.typing import Self from .._core import axis as ca from .._utils import cast, register from . import transform from .transform import AxisTransform __all__ = [ "ArrayTuple", "AxesTuple", "Axis", "Boolean", "IntCategory", "Integer", "Regular", "StrCategory", "Traits", "Variable", "transform", ] def __dir__() -> list[str]: return __all__ def _isstr(value: Any) -> bool: """ Check to see if this is a stringlike or a (nested) iterable of stringlikes """ if isinstance(value, (str, bytes)): return True if hasattr(value, "__iter__"): return all(_isstr(v) for v in value) return False def _opts(**kwargs: bool) -> set[str]: return {k for k, v in kwargs.items() if v} AxCallOrInt = int | Callable[["Axis"], int] @dataclass(order=True, frozen=True) class Traits: underflow: bool = False overflow: bool = False circular: bool = False growth: bool = False continuous: bool = False ordered: bool = False @property def discrete(self) -> bool: "True if axis is not continuous" return not self.continuous T = TypeVar("T", bound="Axis") # Contains common methods and properties to all axes class Axis: __slots__ = ("__dict__", "_ax") _family: object def __init_subclass__(cls, *, family: object) -> None: super().__init_subclass__() cls._family = family def __setattr__(self, attr: str, value: Any) -> None: if attr == "__dict__": self._ax.raw_metadata = value object.__setattr__(self, attr, value) def __getattr__(self, attr: str) -> Any: if attr == "metadata": return None raise AttributeError( f"object {self.__class__.__name__} has no attribute {attr}" ) def __init__( self, ax: Any, metadata: dict[str, Any] | None, __dict__: dict[str, Any] | None, ) -> None: """ ax: the C++ object metadata: the metadata keyword contents __dict__: the __dict__ keyword contents """ self._ax = ax if __dict__ is not None and metadata is not None: raise KeyError( "Cannot provide metadata by keyword and __dict__, use __dict__ only" ) if __dict__ is not None: self._ax.raw_metadata = __dict__ elif metadata is not None: self._ax.raw_metadata["metadata"] = metadata self.__dict__ = self._ax.raw_metadata def __setstate__(self, state: dict[str, Any]) -> None: self._ax = state["_ax"] self.__dict__ = self._ax.raw_metadata def __getstate__(self) -> dict[str, Any]: return {"_ax": self._ax} def __copy__(self) -> Self: other: Self = self.__class__.__new__(self.__class__) other._ax = copy.copy(self._ax) other.__dict__ = other._ax.raw_metadata return other def index(self, value: float | str) -> int: """ Return the fractional index(es) given a value (or values) on the axis. """ if _isstr(value): msg = f"index({value}) cannot be a string for a numerical axis" raise TypeError(msg) return self._ax.index(value) # type: ignore[no-any-return] def value(self, index: float) -> float: """ Return the value(s) given an (fractional) index (or indices). """ return self._ax.value(index) # type: ignore[no-any-return] def bin(self, index: float) -> int | str | tuple[float, float]: """ Return the edges of the bins as a tuple for a continuous axis or the bin value for a non-continuous axis, when given an index. """ return self._ax.bin(index) # type: ignore[no-any-return] __hash__ = None # type: ignore[assignment] def __eq__(self, other: object) -> bool: return hasattr(other, "_ax") and self._ax == other._ax def __ne__(self, other: object) -> bool: return (not hasattr(other, "_ax")) or self._ax != other._ax @classmethod def _convert_cpp(cls, cpp_object: Any) -> Self: nice_ax: Self = cls.__new__(cls) nice_ax._ax = cpp_object nice_ax.__dict__ = cpp_object.raw_metadata return nice_ax def __len__(self) -> int: return self._ax.size # type: ignore[no-any-return] def __iter__( self, ) -> Iterator[float] | Iterator[str] | Iterator[tuple[float, float]]: return self._ax.__iter__() # type: ignore[no-any-return] def _process_callable(self, value: AxCallOrInt | None, *, default: int) -> int: """ This processes a callable in start or stop. None gets replaced by default. """ if value is None: return default if callable(value): return value(self) return value def _process_loc( self, start: AxCallOrInt | None, stop: AxCallOrInt | None ) -> tuple[int, int]: """ Compute start and stop into actual start and stop values in Boost.Histogram. None -> -1 or 0 for start, -> len or len+1 for stop. If start or stop are callable, then call them with the axes. For a non-ordered axes, flow is all or nothing, so this will ensure overflow is turned off if underflow is not None. """ underflow = -1 if self._ax.traits_underflow else 0 overflow = 1 if self._ax.traits_overflow else 0 # Non-ordered axes only use flow if integrating from None to None if not self._ax.traits_ordered and not (start is None and stop is None): overflow = 0 begin = self._process_callable(start, default=underflow) end = self._process_callable(stop, default=len(self) + overflow) return begin, end def __repr__(self) -> str: arg_str = ", ".join(self._repr_args_()) return f"{self.__class__.__name__}({arg_str})" def _repr_args_(self) -> list[str]: """ Return arg options for use in the repr as strings. """ ret = [] if self.metadata is not None: if isinstance(self.metadata, str): ret.append(f"metadata={self.metadata!r}") else: ret.append("metadata=...") return ret @property def traits(self) -> Traits: """ Get traits for the axis - read only properties of a specific axis. """ return Traits( self._ax.traits_underflow, self._ax.traits_overflow, self._ax.traits_circular, self._ax.traits_growth, self._ax.traits_continuous, self._ax.traits_ordered, ) @property def size(self) -> int: """ Return number of bins excluding under- and overflow. """ return self._ax.size # type: ignore[no-any-return] @property def extent(self) -> int: """ Return number of bins including under- and overflow. """ return self._ax.extent # type: ignore[no-any-return] def __getitem__(self, i: AxCallOrInt) -> int | str | tuple[float, float]: """ Access a bin, using normal Python syntax for wraparound. """ # UHI support if callable(i): i = i(self) else: if i < 0: i += self._ax.size if i >= self._ax.size: raise IndexError( f"Out of range access, {i} is more than {self._ax.size}" ) assert not callable(i) return self.bin(i) @property def edges(self) -> np.typing.NDArray[Any]: return self._ax.edges # type: ignore[no-any-return] @property def centers(self) -> np.typing.NDArray[Any]: """ An array of bin centers. """ return self._ax.centers # type: ignore[no-any-return] @property def widths(self) -> np.typing.NDArray[Any]: """ An array of bin widths. """ return self._ax.widths # type: ignore[no-any-return] # Contains all common methods and properties for Regular axes @register( { ca.regular_uoflow, ca.regular_uoflow_growth, ca.regular_uflow, ca.regular_oflow, ca.regular_none, ca.regular_pow, ca.regular_trans, ca.regular_circular, } ) class Regular(Axis, family=boost_histogram): __slots__ = () def __init__( self, bins: int, start: float, stop: float, *, metadata: Any = None, underflow: bool = True, overflow: bool = True, growth: bool = False, circular: bool = False, transform: AxisTransform | None = None, # pylint: disable=redefined-outer-name __dict__: dict[str, Any] | None = None, ): """ Make a regular axis with nice keyword arguments for underflow, overflow, and growth. Parameters ---------- bins : int The number of bins between start and stop start : float The beginning value for the axis stop : float The ending value for the axis metadata : Any Fills .metadata on the axis. underflow : bool = True Enable the underflow bin overflow : bool = True Enable the overflow bin growth : bool = False Allow the axis to grow if a value is encountered out of range. Be careful, the axis will grow as large as needed. circular : bool = False Filling wraps around. transform : Optional[AxisTransform] = None Transform the regular bins (Log, Sqrt, and Pow(v)) __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ options = _opts( underflow=underflow, overflow=overflow, growth=growth, circular=circular ) ax: ca._BaseRegular if transform is not None: if options != {"underflow", "overflow"}: raise KeyError("Transform supplied, cannot change other options") if ( not isinstance(transform, AxisTransform) and AxisTransform in transform.__bases__ # type: ignore[unreachable] ): raise TypeError(f"You must pass an instance, use {transform}()") ax = transform._produce(bins, start, stop) elif options == {"growth", "underflow", "overflow"}: ax = ca.regular_uoflow_growth(bins, start, stop) elif options == {"underflow", "overflow"}: ax = ca.regular_uoflow(bins, start, stop) elif options == {"underflow"}: ax = ca.regular_uflow(bins, start, stop) elif options == {"overflow"}: ax = ca.regular_oflow(bins, start, stop) elif options in ( {"circular", "underflow", "overflow"}, {"circular", "overflow"}, ): # growth=True, underflow=False is also correct ax = ca.regular_circular(bins, start, stop) elif options == set(): ax = ca.regular_none(bins, start, stop) else: raise KeyError("Unsupported collection of options") super().__init__(ax, metadata, __dict__) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" ret = [f"{self.size:g}", f"{self.edges[0]:g}", f"{self.edges[-1]:g}"] if self.traits.growth: ret.append("growth=True") elif self.traits.circular: ret.append("circular=True") else: if not self.traits.underflow: ret.append("underflow=False") if not self.traits.overflow: ret.append("overflow=False") if self.transform is not None: ret.append(f"transform={self.transform}") ret += super()._repr_args_() return ret @property def transform(self) -> AxisTransform | None: if hasattr(self._ax, "transform"): return cast(self, self._ax.transform, AxisTransform) return None @register( { ca.variable_none, ca.variable_uflow, ca.variable_oflow, ca.variable_uoflow, ca.variable_uoflow_growth, ca.variable_circular, } ) class Variable(Axis, family=boost_histogram): __slots__ = () def __init__( self, edges: Iterable[float], *, metadata: Any = None, underflow: bool = True, overflow: bool = True, growth: bool = False, circular: bool = False, __dict__: dict[str, Any] | None = None, ): """ Make an axis with irregularly spaced bins. Provide a list or array of bin edges, and len(edges)-1 bins will be made. Parameters ---------- edges : Array[float] The edges for the bins. There will be one less bin than edges. metadata : object Any Python object to attach to the axis, like a label. underflow : bool = True Enable the underflow bin overflow : bool = True Enable the overflow bin circular : bool = False Enable wraparound growth : bool = False Allow the axis to grow if a value is encountered out of range. Be careful, the axis will grow as large as needed. __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ options = _opts( underflow=underflow, overflow=overflow, growth=growth, circular=circular ) ax: ca._BaseVariable if options == {"growth", "underflow", "overflow"}: ax = ca.variable_uoflow_growth(edges) elif options == {"underflow", "overflow"}: ax = ca.variable_uoflow(edges) elif options == {"underflow"}: ax = ca.variable_uflow(edges) elif options == {"overflow"}: ax = ca.variable_oflow(edges) elif options in ( {"circular", "underflow", "overflow"}, {"circular", "overflow"}, ): # growth=True, underflow=False is also correct ax = ca.variable_circular(edges) elif options == set(): ax = ca.variable_none(edges) else: raise KeyError("Unsupported collection of options") super().__init__(ax, metadata, __dict__) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" if len(self) > 20: ret = [repr(self.edges)] else: args = ", ".join(format(v, "g") for v in self.edges) ret = [f"[{args}]"] if self.traits.growth: ret.append("growth=True") elif self.traits.circular: ret.append("circular=True") else: if not self.traits.underflow: ret.append("underflow=False") if not self.traits.overflow: ret.append("overflow=False") ret += super()._repr_args_() return ret @register( { ca.integer_none, ca.integer_uflow, ca.integer_oflow, ca.integer_uoflow, ca.integer_growth, ca.integer_circular, } ) class Integer(Axis, family=boost_histogram): __slots__ = () def __init__( self, start: int, stop: int, *, metadata: Any = None, underflow: bool = True, overflow: bool = True, growth: bool = False, circular: bool = False, __dict__: dict[str, Any] | None = None, ): """ Make an integer axis, with a collection of consecutive integers. Parameters ---------- start : int The beginning value for the axis stop : int The ending value for the axis. (start-stop) bins will be created. metadata : object Any Python object to attach to the axis, like a label. underflow : bool = True Enable the underflow bin overflow : bool = True Enable the overflow bin circular : bool = False Enable wraparound growth : bool = False Allow the axis to grow if a value is encountered out of range. Be careful, the axis will grow as large as needed. __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ options = _opts( underflow=underflow, overflow=overflow, growth=growth, circular=circular ) ax: ca._BaseInteger # underflow and overflow settings are ignored, integers are always # finite and thus cannot end up in a flow bin when growth is on if "growth" in options and "circular" not in options: ax = ca.integer_growth(start, stop) elif options == {"underflow", "overflow"}: ax = ca.integer_uoflow(start, stop) elif options == {"underflow"}: ax = ca.integer_uflow(start, stop) elif options == {"overflow"}: ax = ca.integer_oflow(start, stop) elif "circular" in options and "growth" not in options: ax = ca.integer_circular(start, stop) elif options == set(): ax = ca.integer_none(start, stop) else: raise KeyError("Unsupported collection of options") super().__init__(ax, metadata, __dict__) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" ret = [f"{self.edges[0]:g}", f"{self.edges[-1]:g}"] if self.traits.growth: ret.append("growth=True") elif self.traits.circular: ret.append("circular=True") else: if not self.traits.underflow: ret.append("underflow=False") if not self.traits.overflow: ret.append("overflow=False") ret += super()._repr_args_() return ret class BaseCategory(Axis, family=boost_histogram): __slots__ = () def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" ret = [] if self.traits.growth: ret.append("growth=True") elif not self.traits.overflow: ret.append("overflow=False") ret += super()._repr_args_() return ret @register({ca.category_str_growth, ca.category_str, ca.category_str_none}) class StrCategory(BaseCategory, family=boost_histogram): __slots__ = () def __init__( self, categories: Iterable[str], *, metadata: Any = None, growth: bool = False, overflow: bool = True, __dict__: dict[str, Any] | None = None, ): """ Make a category axis with strings; items will be added to a predefined list of bins or a growing (with growth=True) list of bins. Parameters ---------- categories : Iterator[str] The bin values in strings. May be empty if growth is enabled. metadata : object Any Python object to attach to the axis, like a label. growth : bool = False Allow the axis to grow if a value is encountered out of range. Be careful, the axis will grow as large as needed. overflow : bool = True Include an overflow bin for "missed" hits. Ignored if growth=True. __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ options = _opts(growth=growth, overflow=overflow) ax: ca._BaseCatStr # henryiii: We currently expand "abc" to "a", "b", "c" - some # Python interfaces protect against that if "growth" in options: ax = ca.category_str_growth(tuple(categories)) elif options == {"overflow"}: ax = ca.category_str(tuple(categories)) elif not options: ax = ca.category_str_none(tuple(categories)) else: raise KeyError("Unsupported collection of options") super().__init__(ax, metadata, __dict__) def index(self, value: float | str) -> int: """ Return the fractional index(es) given a value (or values) on the axis. """ if _isstr(value): return self._ax.index(value) # type: ignore[no-any-return] msg = f"index({value}) must be a string or iterable of strings for a StrCategory axis" raise TypeError(msg) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" args = ", ".join(repr(c) for c in self) ret = [f"[{args}]"] ret += super()._repr_args_() return ret @register({ca.category_int, ca.category_int_growth, ca.category_int_none}) class IntCategory(BaseCategory, family=boost_histogram): __slots__ = () def __init__( self, categories: Iterable[int], *, metadata: Any = None, growth: bool = False, overflow: bool = True, __dict__: dict[str, Any] | None = None, ): """ Make a category axis with ints; items will be added to a predefined list of bins or a growing (with growth=True) list of bins. An empty list is allowed if growth=True. Parameters ---------- categories : Iterable[int] The bin values, either ints or strings. metadata : object Any Python object to attach to the axis, like a label. growth : bool = False Allow the axis to grow if a value is encountered out of range. Be careful, the axis will grow as large as needed. overflow : bool = True Include an overflow bin for "missed" hits. Ignored if growth=True. __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ options = _opts(growth=growth, overflow=overflow) ax: ca._BaseCatInt if "growth" in options: ax = ca.category_int_growth(tuple(categories)) elif options == {"overflow"}: ax = ca.category_int(tuple(categories)) elif not options: ax = ca.category_int_none(tuple(categories)) else: raise KeyError("Unsupported collection of options") super().__init__(ax, metadata, __dict__) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" args = ", ".join(format(c, "g") for c in self) ret = [f"[{args}]"] ret += super()._repr_args_() return ret # Contains all common methods and properties for the boolean axis @register({ca.boolean}) class Boolean(Axis, family=boost_histogram): __slots__ = () def __init__(self, *, metadata: Any = None, __dict__: dict[str, Any] | None = None): """ Make an axis for boolean values. Parameters ---------- metadata : object Any Python object to attach to the axis, like a label. __dict__: Optional[dict[str, Any]] = None The full metadata dictionary """ ax = ca.boolean() super().__init__(ax, metadata, __dict__) def _repr_args_(self) -> list[str]: "Return inner part of signature for use in repr" ret = [] if self.size == 0: ret.append("") elif self.size == 1 and self.centers[0] < 0.75: ret.append("") elif self.size == 1: ret.append("") ret += super()._repr_args_() return ret class MGridOpts(TypedDict): sparse: bool indexing: Literal["ij", "xy"] A = TypeVar("A", bound="ArrayTuple") class ArrayTuple(tuple): # type: ignore[type-arg] __slots__ = () # This is an exhaustive list as of NumPy 1.19 _REDUCTIONS = frozenset(("sum", "any", "all", "min", "max", "prod")) def __getattr__(self, name: str) -> Any: if name in self._REDUCTIONS: return partial(getattr(np, name), np.broadcast_arrays(*self)) return self.__class__(getattr(a, name) for a in self) def __dir__(self) -> list[str]: names = dir(self.__class__) + dir("np.typing.NDArray[Any]") return sorted(n for n in names if not n.startswith("_")) def __call__(self, *args: Any, **kwargs: Any) -> Any: return self.__class__(a(*args, **kwargs) for a in self) def broadcast(self) -> Self: """ The arrays in this tuple will be compressed if possible to save memory. Use this method to broadcast them out into their full memory representation. """ return self.__class__(np.broadcast_arrays(*self)) B = TypeVar("B", bound="AxesTuple") class AxesTuple(tuple): # type: ignore[type-arg] __slots__ = () _MGRIDOPTS: ClassVar[MGridOpts] = {"sparse": True, "indexing": "ij"} def __init__(self, /, _iterable: Iterable[Axis]) -> None: for item in self: if not isinstance(item, Axis): raise TypeError( f"Only an iterable of Axis supported in AxesTuple, got {item}" ) super().__init__() @property def size(self) -> tuple[int, ...]: return tuple(s.size for s in self) @property def extent(self) -> tuple[int, ...]: return tuple(s.extent for s in self) @property def centers(self) -> ArrayTuple: gen = (s.centers for s in self) return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) @property def edges(self) -> ArrayTuple: gen = (s.edges for s in self) return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) @property def widths(self) -> ArrayTuple: gen = (s.widths for s in self) return ArrayTuple(np.meshgrid(*gen, **self._MGRIDOPTS)) def value(self, *indexes: float) -> tuple[float, ...]: if len(indexes) != len(self): raise IndexError( "Must have the same number of arguments as the number of axes" ) return tuple(self[i].value(indexes[i]) for i in range(len(indexes))) def bin(self, *indexes: float) -> tuple[float, ...]: if len(indexes) != len(self): raise IndexError( "Must have the same number of arguments as the number of axes" ) return tuple(self[i].bin(indexes[i]) for i in range(len(indexes))) def index(self, *values: float) -> tuple[float, ...]: # type: ignore[override, override] if len(values) != len(self): raise IndexError( "Must have the same number of arguments as the number of axes" ) return tuple(self[i].index(values[i]) for i in range(len(values))) def __getitem__(self, item: Any) -> Any: result = super().__getitem__(item) return self.__class__(result) if isinstance(result, tuple) else result def __getattr__(self, attr: str) -> tuple[Any, ...]: return tuple(getattr(s, attr) for s in self) def __setattr__(self, attr: str, values: Any) -> None: try: super().__setattr__(attr, values) except AttributeError: for s, v in zip(self, values, strict=True): s.__setattr__(attr, v) value.__doc__ = Axis.value.__doc__ index.__doc__ = Axis.index.__doc__ bin.__doc__ = Axis.bin.__doc__ scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/axis/transform.py000066400000000000000000000116531516627434300266600ustar00rootroot00000000000000from __future__ import annotations import copy from typing import Any, ClassVar, TypeVar import boost_histogram from .._compat.typing import Self from .._core import axis as ca from .._utils import register T = TypeVar("T", bound="AxisTransform") __all__ = ["AxisTransform", "Function", "Pow", "log", "sqrt"] def __dir__() -> list[str]: return __all__ class AxisTransform: __slots__ = ("_this",) _family: ClassVar[object] # pylint: disable=declare-non-slot _this: ca.transform._BaseTransform def __init_subclass__(cls, *, family: object) -> None: super().__init_subclass__() cls._family = family def __copy__(self) -> Self: other: Self = self.__class__.__new__(self.__class__) other._this = copy.copy(self._this) return other @classmethod def _convert_cpp(cls, this: Any) -> Self: self: Self = cls.__new__(cls) self._this = this return self def __repr__(self) -> str: if hasattr(self, "_this"): return repr(self._this) return f"{self.__class__.__name__}() # Missing _this, broken class" def _produce(self, bins: int, start: float, stop: float) -> Any: raise NotImplementedError() def __init__(self) -> None: "Create a new transform instance" raise NotImplementedError() def forward(self, value: float) -> float: "Compute the forward transform" return self._this.forward(value) def inverse(self, value: float) -> float: "Compute the inverse transform" return self._this.inverse(value) @register({ca.transform.pow}) class Pow(AxisTransform, family=boost_histogram): __slots__ = () _type = ca.regular_pow _this: ca.transform.pow # Note: this comes from family _types: ClassVar[set[type[ca.transform.pow]]] def __init__(self, power: float): # pylint: disable=super-init-not-called "Create a new transform instance" (cpp_class,) = self._types self._this = cpp_class(power) @property def power(self) -> float: "The power of the transform" return self._this.power # This one does need to be a normal method def _produce(self, bins: int, start: float, stop: float) -> Any: return self.__class__._type(bins, start, stop, self.power) @register({ca.transform.func_transform}) class Function(AxisTransform, family=boost_histogram): __slots__ = () _type = ca.regular_trans _this: ca.transform.func_transform # Note: this comes from family _types: ClassVar[set[type[ca.transform.func_transform]]] def __init__( # pylint: disable=super-init-not-called self, forward: Any, inverse: Any, *, convert: Any = None, name: str = "" ): """ Create a functional transform from a ctypes double(double) function pointer or any object that provides such an interface through a ``.ctypes`` attribute (such as numba.cfunc). A pure python function *can* be adapted to a ctypes pointer, but please use a Variable axis instead or use something like numba to produce a compiled function pointer. You can manually specify the repr name with ``name=``. Example of Numba use: --------------------- @numba.cfunc(numba.float64(numba.float64,)) def exp(x): return math.exp(x) @numba.cfunc(numba.float64(numba.float64,)) def log(x): return math.log(x) Example of slow CTypes use: --------------------------- ftype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) log = ftype(math.log) exp = ftype(math.exp) Now you can supply these functions, and you will get a high performance transformation axis. You can also supply an optional conversion function; this will take the input forward and inverse and call them before producing a transform. This enables pickling, as well, since ctypes pointers are not picklable. A few common utilities have been supplied: * ``convert.numba``: Compile using numba (required) * ``convert.python``: Just call the Python function (15-90x slower than compiled) See also -------- * ``Numbify(forward, inverse, *, name='')``: Uses convert=convert.numba * ``PythonFunction(forward, inverse, *, name='')``: Uses convert=convert.python """ (cpp_class,) = self._types self._this = cpp_class(forward, inverse, convert, name) # This one does need to be a normal method def _produce(self, bins: int, start: float, stop: float) -> Any: return self.__class__._type(bins, start, stop, self._this) def _internal_conversion(name: str) -> Any: return getattr(ca.transform, name) sqrt = Function("_sqrt_fn", "_sq_fn", convert=_internal_conversion, name="sqrt") log = Function("_log_fn", "_exp_fn", convert=_internal_conversion, name="log") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/histogram.py000066400000000000000000001707441516627434300257050ustar00rootroot00000000000000from __future__ import annotations import collections.abc import copy import enum import logging import sys import threading import typing import warnings from collections.abc import Callable, Iterable, Mapping from os import cpu_count from typing import ( TYPE_CHECKING, Any, ClassVar, Literal, NewType, SupportsIndex, TypeAlias, TypeVar, ) import numpy as np import boost_histogram from boost_histogram import _core from . import serialization from . import storage as bhs from ._compat.typing import Self from ._utils import cast, register from .axis import AxesTuple, Axis, Variable from .storage import Double, Storage from .view import MeanView, WeightedMeanView, WeightedSumView, _to_view if TYPE_CHECKING: from types import EllipsisType from .typing import ( Accumulator, ArrayLike, CppHistogram, Mean, RebinProtocol, WeightedMean, WeightedSum, ) try: from . import _core except ImportError as err: if "_core" not in str(err): raise new_msg = "Did you forget to compile boost-histogram? Use CMake or scikit-build-core to build, see the readme." if sys.version_info >= (3, 11): err.add_note(new_msg) raise total_msg = f"{err}\n{new_msg}" new_exception = type(err)(new_msg, name=err.name, path=err.path) raise new_exception from err # This is a StrEnum as defined in Python 3.11 class Kind(str, enum.Enum): COUNT = "COUNT" MEAN = "MEAN" __str__ = str.__str__ __all__ = [ "Histogram", "IndexingExpr", "Kind", ] def __dir__() -> list[str]: return __all__ NOTHING = object() _histograms: set[type[CppHistogram]] = { _core.hist.any_double, _core.hist.any_int64, _core.hist.any_atomic_int64, _core.hist.any_unlimited, _core.hist.any_weight, _core.hist.any_mean, _core.hist.any_weighted_mean, _core.hist.any_multi_cell, } logger = logging.getLogger(__name__) CppAxis = NewType("CppAxis", object) SimpleIndexing: TypeAlias = "SupportsIndex | slice | RebinProtocol" InnerIndexing: TypeAlias = "SimpleIndexing | Callable[[Axis], int]" FullInnerIndexing: TypeAlias = "InnerIndexing | list[InnerIndexing]" IndexingWithMapping: TypeAlias = "FullInnerIndexing | Mapping[int, FullInnerIndexing]" IndexingExpr: TypeAlias = ( "IndexingWithMapping | tuple[IndexingWithMapping, ...] | EllipsisType" ) T = TypeVar("T") IntHists = TypeVar( "IntHists", bound="Histogram[bhs.AtomicInt64] | Histogram[bhs.Int64]" ) FloatHists = TypeVar( "FloatHists", bound="Histogram[bhs.Double] | Histogram[bhs.Unlimited]" ) ListHists = TypeVar("ListHists", bound="Histogram[bhs.MultiCell]") WeightHists = TypeVar("WeightHists", bound="Histogram[bhs.Weight]") MeanHists = TypeVar("MeanHists", bound="Histogram[bhs.Mean]") WeightedMeanHists = TypeVar("WeightedMeanHists", bound="Histogram[bhs.WeightedMean]") @typing.overload def _fill_cast( value: tuple[T, ...] | list[T], *, inner: Literal[False] = False ) -> tuple[T | np.typing.NDArray[Any], ...]: ... @typing.overload def _fill_cast(value: T, *, inner: bool = False) -> T | np.typing.NDArray[Any]: ... def _fill_cast( value: Any, *, inner: bool = False ) -> Any | np.typing.NDArray[Any] | tuple[Any | np.typing.NDArray[Any], ...]: """ Convert to NumPy arrays. Some buffer objects do not get converted by forcecast. If not called by itself (inner=False), then will work through one level of tuple/list. """ if value is None or isinstance(value, (str, bytes)): return value if not inner and isinstance(value, (tuple, list)): return tuple(_fill_cast(a, inner=True) for a in value) if hasattr(value, "__iter__") or hasattr(value, "__array__"): return np.asarray(value) return value def mean_storage_sample_check(sample: ArrayLike | None) -> None: if sample is None: raise TypeError("Sample key-argument (sample=) needs to be provided.") seqs = (collections.abc.Sequence, np.ndarray) msg1 = f"Sample key-argument needs to be a sequence, {sample.__class__.__name__} given." if isinstance(sample, str) and not isinstance(sample, seqs): raise ValueError(msg1) sample_dim = np.array(sample).ndim msg2 = f"Sample key-argument needs to be 1 dimensional, {sample_dim} given." if sample_dim != 1: raise ValueError(msg2) def _arg_shortcut(item: tuple[int, float, float] | Axis | CppAxis) -> CppAxis: if isinstance(item, tuple) and len(item) == 3: msg = "Using () directly in constructor is a developer shortcut and will be removed in a future version" warnings.warn(msg, FutureWarning, stacklevel=4) return _core.axis.regular_uoflow(item[0], item[1], item[2]) # type: ignore[return-value] if isinstance(item, Axis): return item._ax # type: ignore[no-any-return] raise TypeError("Only axes supported in histogram constructor") def _expand_ellipsis(indexes: Iterable[Any], rank: int) -> list[Any]: indexes = list(indexes) number_ellipses = indexes.count(Ellipsis) if number_ellipses == 0: return indexes if number_ellipses == 1: index = indexes.index(Ellipsis) additional = rank + 1 - len(indexes) if additional < 0: raise IndexError("too many indices for histogram") # Fill out the ellipsis with empty slices return indexes[:index] + [slice(None)] * additional + indexes[index + 1 :] raise IndexError("an index can only have a single ellipsis ('...')") def _combine_group_contents( new_view: np.typing.NDArray[Any], reduced_view: np.typing.NDArray[Any], i: int, j: int, jj: int, ) -> None: """ Combine two views into one, in-place. This is used for threaded filling. """ pos = [slice(None)] * (i) if new_view.dtype.names: for field in new_view.dtype.names: new_view[(*pos, jj, ...)][field] += reduced_view[(*pos, j, ...)][field] # type: ignore[arg-type] else: new_view[(*pos, jj, ...)] += reduced_view[(*pos, j, ...)] # type: ignore[arg-type] H = TypeVar("H", bound="Histogram[Any]") S = TypeVar("S", bound="Storage") NO_METADATA = object() # We currently do not cast *to* a histogram, but this is consistent # and could be used later. @register(_histograms) # type: ignore[arg-type] class Histogram(typing.Generic[S]): # Note this is a __slots__ __dict__ class! __slots__ = ( "__dict__", "_hist", "axes", ) # .metadata and ._variance_known are part of the dict. # .metadata will not be placed in the dict if not passed. _family: ClassVar[object] = boost_histogram axes: AxesTuple _hist: CppHistogram _variance_known: bool def __init_subclass__(cls, *, family: object | None = None) -> None: """ Sets the family for the histogram. This should be a unique object (such as the main module of your package) that is consistently set across all subclasses. When converting back from C++, casting will try to always pick the best matching family from the loaded subclasses for Axis and such. """ super().__init_subclass__() cls._family = family if family is not None else object() @typing.overload def __init__( self, arg: Histogram[S], /, *, metadata: Any = ..., __dict__: Any = ... ) -> None: ... @typing.overload def __init__( self, arg: dict[str, Any], /, *, metadata: Any = ..., __dict__: Any = ... ) -> None: ... @typing.overload def __init__( self, arg: CppHistogram, /, *, metadata: Any = ..., __dict__: Any = ... ) -> None: ... @typing.overload def __init__( self, *axes: Axis | CppAxis, storage: S, metadata: Any = ..., __dict__: Any = ..., ) -> None: ... @typing.overload def __init__( self: Histogram[Double], *axes: Axis | CppAxis, storage: None = ..., metadata: Any = ..., __dict__: Any = ..., ) -> None: ... def __init__( self, *axes: Axis | CppAxis | Histogram[Any] | CppHistogram | dict[str, Any], storage: S | None = None, metadata: Any = NO_METADATA, __dict__: Any = None, ) -> None: """ Construct a new histogram. If you pass in a single argument, this will be treated as a histogram and this will convert the histogram to this type of histogram. Parameters ---------- *args : Axis Provide 1 or more axis instances. storage : Storage = bh.storage.Double() Select a storage to use in the histogram metadata : Any = None Data that is passed along if a new histogram is created. No not use in new code; use ``__dict__`` instead. __dict__ : Any = None Better way to set metadata. """ self._variance_known = True storage_err_msg = "storage= is not allowed with conversion constructor" if metadata is not NO_METADATA and __dict__: msg = ( "Can't set both metadata and __dict__. Set the 'metadata' key instead." ) raise TypeError(msg) if metadata is not NO_METADATA: __dict__ = {"metadata": metadata} if __dict__ is None: __dict__ = {} # Allow construction from a raw histogram object (internal) if len(axes) == 1 and isinstance(axes[0], tuple(_histograms)): if storage is not None: raise TypeError(storage_err_msg) cpp_hist: CppHistogram = axes[0] # type: ignore[assignment] self._from_histogram_cpp(cpp_hist, __dict__=__dict__) return # If we construct with another Histogram as the only positional argument, # support that too if len(axes) == 1 and isinstance(axes[0], Histogram): if storage is not None: raise TypeError(storage_err_msg) self._from_histogram_object(axes[0], __dict__=__dict__) return # Support objects that provide a to_boost method, like Uproot if len(axes) == 1 and hasattr(axes[0], "_to_boost_histogram_"): if storage is not None: raise TypeError(storage_err_msg) self._from_histogram_object( axes[0]._to_boost_histogram_(), __dict__=__dict__ ) return # Support UHI if len(axes) == 1 and isinstance(axes[0], dict) and "uhi_schema" in axes[0]: if storage is not None: raise TypeError(storage_err_msg) self._from_histogram_object( serialization.from_uhi(axes[0]), __dict__=__dict__ ) return resolved_storage = Double() if storage is None else storage self.__dict__.update(__dict__) # Check for missed parenthesis or incorrect types if not isinstance(resolved_storage, Storage): msg_storage = ( # type: ignore[unreachable] "Passing in an initialized storage has been removed. Please add ()." ) msg_unknown = "Only storages allowed in storage argument" raise KeyError( msg_storage if issubclass(resolved_storage, Storage) else msg_unknown ) # Allow a tuple to represent a regular axis axes = tuple(_arg_shortcut(arg) for arg in axes) # type: ignore[arg-type] if len(axes) > _core.hist._axes_limit: msg = f"Too many axes, must be less than {_core.hist._axes_limit}" raise IndexError(msg) # Check all available histograms, and if the storage matches, return that one for h in _histograms: if isinstance(resolved_storage, h._storage_type): self._hist = h(axes, resolved_storage) # type: ignore[arg-type] self.axes = self._generate_axes_() return raise TypeError("Unsupported storage") @classmethod def _clone( cls, _hist: Histogram[Any] | CppHistogram, *, other: Histogram[Any] | None = None, memo: Any = NOTHING, ) -> Self: """ Clone a histogram (possibly of a different base). Does not trigger __init__. This will copy data from `other=` if non-None, otherwise metadata gets copied from the input. """ self = cls.__new__(cls) if isinstance(_hist, tuple(_histograms)): self._from_histogram_cpp(_hist, __dict__={}) # type: ignore[arg-type] if other is not None: return cls._clone(self, other=other, memo=memo) return self assert isinstance(_hist, Histogram) if other is None: other = _hist if memo is NOTHING: dict_copy = copy.copy(other.__dict__) else: dict_copy = copy.deepcopy(other.__dict__, memo) self._from_histogram_object(_hist, __dict__=dict_copy) for ax in self.axes: if memo is NOTHING: ax._ax.raw_metadata = copy.copy(ax._ax.raw_metadata) else: ax._ax.raw_metadata = copy.deepcopy(ax._ax.raw_metadata, memo) ax.__dict__ = ax._ax.raw_metadata return self def _new_hist(self, _hist: CppHistogram, memo: Any = NOTHING) -> Self: """ Return a new histogram given a new _hist, copying current metadata. """ return self.__class__._clone(_hist, other=self, memo=memo) def _from_histogram_cpp( self, other: CppHistogram, *, __dict__: dict[str, Any] ) -> None: """ Import a Cpp histogram. """ self._variance_known = True self._hist = other self.__dict__.update(__dict__) self.axes = self._generate_axes_() def _from_histogram_object( self, other: Histogram[S], *, __dict__: dict[str, Any] ) -> None: """ Convert self into a new histogram object based on another, possibly converting from a different subclass. """ self._hist = other._hist self.__dict__ = copy.copy(other.__dict__) self.axes = self._generate_axes_() for ax in self.axes: ax.__dict__.update(ax._ax.raw_metadata) self.__dict__.update(other.__dict__) self.__dict__.update(__dict__) # Allow custom behavior on either "from" or "to" other._export_bh_(self) self._import_bh_() def _import_bh_(self) -> None: """ If any post-processing is needed to pass a histogram between libraries, a subclass can implement it here. self is the new instance in the current (converted-to) class. """ @classmethod def _export_bh_(cls, self: Histogram[Any]) -> None: """ If any preparation is needed to pass a histogram between libraries, a subclass can implement it here. cls is the current class being converted from, and self is the instance in the class being converted to. """ def _generate_axes_(self) -> AxesTuple: """ This is called to fill in the axes. Subclasses can override it if they need to change the axes tuple. """ return AxesTuple(self._axis(i) for i in range(self.ndim)) # Backward compat for metadata default def __getattr__(self, name: str) -> Any: if name == "metadata": msg = ".metadata was not set, returning None instead of Attribute error, boost-histogram 1.7+ will error." warnings.warn(msg, FutureWarning, stacklevel=2) return None return super().__getattribute__(name) def _to_uhi_(self) -> dict[str, Any]: """ Convert to a UHI histogram. """ return serialization.to_uhi(self) @classmethod def _from_uhi_(cls, inp: dict[str, Any], /) -> Self: """ Convert from a UHI histogram. """ return cls(serialization.from_uhi(inp)) @property def ndim(self) -> int: """ Number of axes (dimensions) of the histogram. """ return self._hist.rank() @typing.overload def view( self: Histogram[bhs.Double] | Histogram[bhs.MultiCell] | Histogram[bhs.Unlimited], flow: bool = False, ) -> np.typing.NDArray[np.float64]: ... @typing.overload def view( self: Histogram[bhs.Int64] | Histogram[bhs.AtomicInt64], flow: bool = False, ) -> np.typing.NDArray[np.int64]: ... @typing.overload def view(self: Histogram[bhs.Weight], flow: bool = False) -> WeightedSumView: ... @typing.overload def view(self: Histogram[bhs.Mean], flow: bool = False) -> MeanView: ... @typing.overload def view( self: Histogram[bhs.WeightedMean], flow: bool = False ) -> WeightedMeanView: ... @typing.overload def view( self: Histogram[Any], flow: bool = False ) -> ( np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64] | WeightedSumView | WeightedMeanView | MeanView ): ... def view( self, flow: bool = False ) -> ( np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64] | WeightedSumView | WeightedMeanView | MeanView ): """ Return a view into the data, optionally with overflow turned on. """ return _to_view(self._hist.view(flow)) def __array__( self, dtype: np.typing.DTypeLike | None = None, *, # pylint: disable-next=redefined-outer-name copy: bool | None = None, ) -> np.typing.NDArray[Any]: # The copy kw is new in NumPy 2.0 kwargs = {} if copy is not None: kwargs["copy"] = copy return np.asarray(self.view(False), dtype=dtype, **kwargs) # type: ignore[call-overload, no-any-return] __hash__ = None # type: ignore[assignment] def __eq__(self, other: object) -> bool: return hasattr(other, "_hist") and self._hist == other._hist def __ne__(self, other: object) -> bool: return (not hasattr(other, "_hist")) or self._hist != other._hist def __add__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: result = self.copy(deep=False) return result.__iadd__(other) def __iadd__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: if isinstance(other, (int, float)) and other == 0: return self self._compute_inplace_op("__iadd__", other) # Addition may change the axes if they can grow self.axes = self._generate_axes_() return self def __radd__(self, other: np.typing.NDArray[Any] | float) -> Self: return self + other def __sub__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: result = self.copy(deep=False) return result.__isub__(other) def __isub__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: if isinstance(other, (int, float)) and other == 0: return self self._compute_inplace_op("__isub__", other) self.axes = self._generate_axes_() return self # If these fail, the underlying object throws the correct error def __mul__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: result = self.copy(deep=False) return result._compute_inplace_op("__imul__", other) def __rmul__(self, other: np.typing.NDArray[Any] | float) -> Self: return self * other def __truediv__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: result = self.copy(deep=False) return result._compute_inplace_op("__itruediv__", other) def __div__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: result = self.copy(deep=False) return result._compute_inplace_op("__idiv__", other) def __idiv__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: return self._compute_inplace_op("__idiv__", other) def __itruediv__( self, other: Histogram[S] | np.typing.NDArray[Any] | float ) -> Self: return self._compute_inplace_op("__itruediv__", other) def __imul__(self, other: Histogram[S] | np.typing.NDArray[Any] | float) -> Self: return self._compute_inplace_op("__imul__", other) def _compute_inplace_op( self, name: str, other: Histogram[S] | np.typing.NDArray[Any] | float ) -> Self: # Also takes CppHistogram, but that confuses mypy because it's hard to pick out if isinstance(other, Histogram): getattr(self._hist, name)(other._hist) elif isinstance(other, tuple(_histograms)): getattr(self._hist, name)(other) elif hasattr(other, "shape") and other.shape: assert not isinstance(other, float) if len(other.shape) != self.ndim: msg = f"Number of dimensions {len(other.shape)} must match histogram {self.ndim}" raise ValueError(msg) if all(a in {b, 1} for a, b in zip(other.shape, self.shape, strict=False)): view = self.view(flow=False) getattr(view, name)(other) elif all( a in {b, 1} for a, b in zip(other.shape, self.axes.extent, strict=False) ): view = self.view(flow=True) getattr(view, name)(other) else: msg = f"Wrong shape {other.shape}, expected {self.shape} or {self.axes.extent}" raise ValueError(msg) else: view = self.view(flow=True) getattr(view, name)(other) self._variance_known = False return self # TODO: Marked as too complex by flake8. Should be factored out a bit. def fill( self, *args: ArrayLike | str, weight: ArrayLike | None = None, sample: ArrayLike | None = None, threads: int | None = None, ) -> Self: """ Insert data into the histogram. Parameters ---------- *args : Union[Array[float], Array[int], Array[str], float, int, str] Provide one value or array per dimension. weight : list[Union[Array[float], Array[int], float, int, str]]] Provide weights (only if the histogram storage supports it) sample : list[Union[Array[float], Array[int], Array[str], float, int, str]]] Provide samples (only if the histogram storage supports it) threads : Optional[int] Fill with threads. Defaults to None, which does not activate threaded filling. Using 0 will automatically pick the number of available threads (usually two per core). """ if self._hist._storage_type is _core.storage.mean: mean_storage_sample_check(sample) if ( self._hist._storage_type not in { _core.storage.weight, _core.storage.mean, _core.storage.weighted_mean, } and weight is not None ): self._variance_known = False if self._hist._storage_type is _core.storage.multi_cell: # Use weight keyword for MultiCell filling even though it uses sample on the C++ backend sample = weight weight = None # Convert to NumPy arrays args_ars = _fill_cast(args) weight_ars = _fill_cast(weight) sample_ars = _fill_cast(sample) # Broadcast scalar positional args to match sample length when sample is an array. # This allows e.g. h.fill(0, sample=[1, 2, 3]) to work for Mean/WeightedMean storage. if sample_ars is not None: sample_arr = np.asarray(sample_ars) if sample_arr.ndim > 0: sample_len = len(sample_arr) if sample_len > 1: args_ars = tuple( np.full(sample_len, a) if np.ndim(a) == 0 else a for a in args_ars ) if threads == 0: threads = cpu_count() if threads is None or threads == 1: self._hist.fill(*args_ars, weight=weight_ars, sample=sample_ars) return self if self._hist._storage_type in { _core.storage.mean, _core.storage.weighted_mean, }: raise RuntimeError("Mean histograms do not support threaded filling") data: list[list[np.typing.NDArray[Any]] | list[str]] = [] for a in args_ars: if isinstance(a, str): data.append([a] * threads) else: data.append(np.array_split(np.asarray(a), threads)) weights: list[Any] if weight is None or np.isscalar(weight): assert threads is not None weights = [weight_ars] * threads else: weights = np.array_split(np.asarray(weight_ars), threads) samples: list[Any] if sample_ars is None or np.isscalar(sample_ars): assert threads is not None samples = [sample_ars] * threads else: samples = np.array_split(np.asarray(sample_ars), threads) if self._hist._storage_type is _core.storage.atomic_int64: def fun( weight: ArrayLike | None, sample: ArrayLike | None, *args: np.typing.NDArray[Any], ) -> None: self._hist.fill(*args, weight=weight, sample=sample) else: sum_lock = threading.Lock() def fun( weight: ArrayLike | None, sample: ArrayLike | None, *args: np.typing.NDArray[Any], ) -> None: local_hist = copy.copy(self._hist) local_hist.reset() local_hist.fill(*args, weight=weight, sample=sample) with sum_lock: self._hist += local_hist thread_list = [ threading.Thread(target=fun, args=arrays) for arrays in zip(weights, samples, *data, strict=False) ] for thread in thread_list: thread.start() for thread in thread_list: thread.join() return self def __str__(self) -> str: """ A rendering of the histogram is made using ASCII or unicode characters (whatever is supported by the terminal). What exactly is displayed is still experimental. Do not rely on any particular rendering. """ # TODO check the terminal width and adjust the presentation # only use for 1D, fall back to repr for ND if self._hist.rank() != 1: return repr(self) s = str(self._hist) # get rid of first line and last character return s[s.index("\n") + 1 : -1] def _axis(self, i: int = 0) -> Axis: """ Get N-th axis. """ return cast(self, self._hist.axis(i), Axis) @property def storage_type(self) -> type[S]: return cast(self, self._hist._storage_type, Storage) # type: ignore[return-value] @property def _storage_type(self) -> type[S]: warnings.warn( "Accessing storage type has changed from _storage_type to storage_type, and will be removed in future.", FutureWarning, stacklevel=2, ) return cast(self, self._hist._storage_type, Storage) # type: ignore[return-value] @property def storage(self) -> S: """ New storage matching the one the histogram was constructed with. """ if issubclass(self.storage_type, bhs.MultiCell): return self.storage_type(self._hist.nelem()) # type: ignore[attr-defined] return self.storage_type() def _reduce(self, *args: Any) -> Self: return self._new_hist(self._hist.reduce(*args)) def __copy__(self) -> Self: return self._new_hist(copy.copy(self._hist)) def __deepcopy__(self, memo: Any) -> Self: return self._new_hist(copy.deepcopy(self._hist), memo=memo) def __getstate__(self) -> tuple[int, dict[str, Any]]: """ Version 0.8: metadata added Version 0.11: version added and set to 0. metadata/_hist replaced with dict. Version 0.12: _variance_known is now in the dict (no format change) ``dict`` contains __dict__ with added "_hist" """ local_dict = copy.copy(self.__dict__) local_dict["_hist"] = self._hist # Version 0 of boost-histogram pickle state return (0, local_dict) def __setstate__(self, state: Any) -> None: if isinstance(state, tuple): if state[0] == 0: for key, value in state[1].items(): setattr(self, key, value) # Added in 0.12 if "_variance_known" not in state[1]: self._variance_known = True else: msg = f"Cannot open boost-histogram pickle v{state[0]}" raise RuntimeError(msg) else: # Classic (0.10 and before) state self._hist = state["_hist"] self._variance_known = True self.metadata = state.get("metadata", None) for i in range(self._hist.rank()): self._hist.axis(i).raw_metadata = { "metadata": self._hist.axis(i).raw_metadata } self.axes = self._generate_axes_() def __repr__(self) -> str: newline = "\n " first_newline = newline if len(self.axes) > 1 else "" storage_newline = ( newline if len(self.axes) > 1 else " " if len(self.axes) > 0 else "" ) sep = "," if len(self.axes) > 0 else "" ret = f"{self.__class__.__name__}({first_newline}" ret += f",{newline}".join(repr(ax) for ax in self.axes) ret += f"{sep}{storage_newline}storage={self.storage}" ret += ")" outer = self.sum(flow=True) if outer: inner = self.sum(flow=False) ret += f" # Sum: {inner}" if inner != outer: ret += f" ({outer} with flow)" return ret def _compute_uhi_index(self, index: InnerIndexing, axis: int) -> SimpleIndexing: """ Converts an expression that contains UHI locators to one that does not. """ # Support sum and rebin directly if index is sum or hasattr(index, "factor"): # type: ignore[comparison-overlap,redundant-expr] return slice(None, None, index) # General locators # Note that MyPy doesn't like these very much - the fix # will be to properly set input types if callable(index): return index(self.axes[axis]) if isinstance(index, float): raise TypeError(f"Index {index} must be an integer, not float") if isinstance(index, SupportsIndex): if abs(int(index)) >= self._hist.axis(axis).size: raise IndexError("histogram index is out of range") return int(index) % self._hist.axis(axis).size return index def _compute_commonindex( self, index: IndexingExpr ) -> list[SupportsIndex | slice | Mapping[int, SupportsIndex | slice]]: """ Takes indices and returns two iterables; one is a tuple or dict of the original, Ellipsis expanded index, and the other returns index, operation value pairs. """ indexes: list[Any] # Shorten the computations with direct access to raw object hist = self._hist # Support dict access if hasattr(index, "items"): indexes = [slice(None)] * hist.rank() for k, v in index.items(): indexes[k] = v # Normalize -> h[i] == h[i,] else: tuple_index = (index,) if not isinstance(index, tuple) else index # Now a list indexes = _expand_ellipsis(tuple_index, hist.rank()) if len(indexes) != hist.rank(): raise IndexError("Wrong number of indices for histogram") # Allow [bh.loc(...)] to work # TODO: could be nicer making a new list via a comprehension for i in range(len(indexes)): # pylint: disable=consider-using-enumerate # Support list of UHI indexers if isinstance(indexes[i], list): indexes[i] = [self._compute_uhi_index(ind, i) for ind in indexes[i]] else: indexes[i] = self._compute_uhi_index(indexes[i], i) return indexes @typing.overload def to_numpy( self, flow: bool = ..., *, dd: Literal[False] = ..., view: bool = ... ) -> tuple[np.typing.NDArray[Any], ...]: ... @typing.overload def to_numpy( self, flow: bool = ..., *, dd: Literal[True], view: bool = ... ) -> tuple[np.typing.NDArray[Any], tuple[np.typing.NDArray[np.float64], ...]]: ... @typing.overload def to_numpy( self, flow: bool = ..., *, dd: bool, view: bool = ... ) -> ( tuple[np.typing.NDArray[Any], ...] | tuple[np.typing.NDArray[Any], tuple[np.typing.NDArray[np.float64], ...]] ): ... def to_numpy( self, flow: bool = False, *, dd: bool = False, view: bool = False ) -> ( tuple[np.typing.NDArray[Any], ...] | tuple[np.typing.NDArray[Any], tuple[np.typing.NDArray[np.float64], ...]] ): """ Convert to a NumPy style tuple of return arrays. Edges are converted to match NumPy standards, with upper edge inclusive, unlike boost-histogram, where upper edge is exclusive. Parameters ---------- flow : bool = False Include the flow bins. dd : bool = False Use the histogramdd return syntax, where the edges are in a tuple. Otherwise, this is the histogram/histogram2d return style. view : bool = False The behavior for the return value. By default, this will return array of the values only regardless of the storage (which is all NumPy's histogram function can do). view=True will return the boost-histogram view of the storage. Return ------ contents : Array[Any] The bin contents *edges : Array[float] The edges for each dimension """ hist, *edges = self._hist.to_numpy(flow) hist = self.view(flow=flow) if view else self.values(flow=flow) return (hist, edges) if dd else (hist, *edges) # type: ignore[return-value] def copy(self, *, deep: bool = True) -> Self: """ Make a copy of the histogram. Defaults to making a deep copy (axis metadata copied); use deep=False to avoid making a copy of axis metadata. """ return copy.deepcopy(self) if deep else copy.copy(self) def reset(self) -> Self: """ Clear the bin counters. """ self._hist.reset() return self def empty(self, flow: bool = False) -> bool: """ Check to see if the histogram has any non-default values. You can use flow=True to check flow bins too. """ return self._hist.empty(flow) @typing.overload def sum( self: Histogram[bhs.Double] | Histogram[bhs.Int64] | Histogram[bhs.AtomicInt64] | Histogram[bhs.Unlimited], flow: bool = False, ) -> float: ... @typing.overload def sum(self: Histogram[bhs.MultiCell], flow: bool = False) -> list[float]: ... @typing.overload def sum(self: Histogram[bhs.Weight], flow: bool = False) -> WeightedSum: ... @typing.overload def sum(self: Histogram[bhs.Mean], flow: bool = False) -> Mean: ... @typing.overload def sum(self: Histogram[bhs.WeightedMean], flow: bool = False) -> WeightedMean: ... @typing.overload def sum(self, flow: bool = False) -> float | Accumulator | list[float]: ... def sum(self, flow: bool = False) -> float | Accumulator | list[float]: """ Compute the sum over the histogram bins (optionally including the flow bins). """ return self._hist.sum(flow) # type: ignore[no-any-return] @property def size(self) -> int: """ Total number of bins in the histogram (including underflow/overflow). """ return self._hist.size() @property def shape(self) -> tuple[int, ...]: """ Tuple of axis sizes (not including underflow/overflow). """ return self.axes.size @typing.overload def __getitem__(self: FloatHists, index: IndexingExpr) -> FloatHists | float: ... @typing.overload def __getitem__(self: IntHists, index: IndexingExpr) -> IntHists | int: ... @typing.overload def __getitem__( self: ListHists, index: IndexingExpr ) -> ListHists | list[float]: ... @typing.overload def __getitem__( self: WeightHists, index: IndexingExpr ) -> WeightHists | WeightedSum: ... @typing.overload def __getitem__(self: MeanHists, index: IndexingExpr) -> MeanHists | Mean: ... @typing.overload def __getitem__( self: WeightedMeanHists, index: IndexingExpr ) -> WeightedMeanHists | WeightedMean: ... @typing.overload def __getitem__( self, index: IndexingExpr ) -> Self | float | list[float] | int | Accumulator: ... def __getitem__( self, index: IndexingExpr ) -> Self | float | Accumulator | list[float] | int: indexes = self._compute_commonindex(index) # Early return for all-integer case if not hasattr(indexes, "items") and all( isinstance(a, SupportsIndex) for a in indexes ): return self._hist.at(*indexes) # type: ignore[no-any-return, arg-type] integrations = set[int]() slices = list[_core.algorithm.reduce_command]() pick_each = dict[int, int]() pick_set = dict[int, list[int]]() reduced: CppHistogram | None = None for i, ind in enumerate(indexes): match ind: case SupportsIndex(): pick_each[i] = ind.__index__() + ( 1 if self.axes[i].traits.underflow else 0 ) case collections.abc.Sequence(): pick_set[i] = list(ind) case slice(start=start, stop=stop, step=step): reduced, new_slices, new_integrations = self._handle_slice( i, start, stop, step, reduced ) slices.extend(new_slices) integrations.update(new_integrations) case _: raise IndexError( "Must be a slice, an integer, or follow the locator protocol." ) if (slices or pick_set or pick_each or integrations) and not reduced: reduced = self._hist elif not reduced: reduced = copy.copy(self._hist) if pick_each: tuple_slice = tuple( pick_each.get(i, slice(None)) for i in range(reduced.rank()) ) if isinstance(self._hist, _core.hist.any_multi_cell): # View of multi cell histograms has as first (index 0) dimension the cell index # Add a full slice to the beginning of the slicing expression to adept for this cell index # e.g. a slice like [0, :, 3] is converted to [:, 0, :, 3] tuple_slice = (slice(None, None, None), *tuple_slice) logger.debug("Slices for pick each: %s", tuple_slice) axes = [ reduced.axis(i) for i in range(reduced.rank()) if i not in pick_each ] logger.debug("Axes: %s", axes) new_reduced: _core.hist._BaseHistogram | _core.hist.any_multi_cell = ( reduced.__class__(axes) ) if isinstance(reduced, _core.hist.any_multi_cell) and isinstance( new_reduced, _core.hist.any_multi_cell ): # The constructor in reduced.__class__(axes) does not take care of the number of cells. # If reduced is a multi cell histogram, we have to set the number of cells per bin manually for new_reduced new_reduced.reset_nelem(reduced.nelem()) new_reduced.view(flow=True)[...] = reduced.view(flow=True)[tuple_slice] reduced = new_reduced integrations = {i - sum(j <= i for j in pick_each) for i in integrations} pick_set = { i - sum(j <= i for j in pick_each): v for i, v in pick_set.items() } for slice_ in slices: slice_.iaxis -= sum(j <= slice_.iaxis for j in pick_each) if slices: logger.debug("Reduce with %s", slices) reduced = reduced.reduce(*slices) if pick_set: warnings.warn( "List indexing selection is experimental. Removed bins are not placed in overflow.", stacklevel=2, ) logger.debug("Slices for picking sets: %s", pick_set) axes = [reduced.axis(i) for i in range(reduced.rank())] reduced_view = reduced.view(flow=True) for i in pick_set: selection = copy.copy(pick_set[i]) ax = reduced.axis(i) if ax.traits_ordered: msg = f"Axis {i} is not a categorical axis, cannot pick with list: {ax}" raise RuntimeError(msg) if ax.traits_overflow and ax.size not in pick_set[i]: selection.append(ax.size) new_axis = axes[i].__class__([axes[i].value(j) for j in pick_set[i]]) # type: ignore[call-arg] new_axis.raw_metadata = axes[i].raw_metadata axes[i] = new_axis reduced_view = np.take(reduced_view, selection, axis=i) logger.debug("Axes: %s", axes) new_reduced = reduced.__class__(axes) new_reduced.view(flow=True)[...] = reduced_view reduced = new_reduced if integrations: projections = [i for i in range(reduced.rank()) if i not in integrations] reduced = reduced.project(*projections) return self._new_hist(reduced) if reduced.rank() > 0 else reduced.sum(flow=True) def _handle_slice( self, i: int, start: int | None, stop: int | None, step: int | slice | Mapping[int, SupportsIndex | slice] | Callable[[Any], int] | None, reduced: CppHistogram | None, ) -> tuple[CppHistogram | None, list[_core.algorithm.reduce_command], set[int]]: if any(isinstance(v, slice) for v in (start, stop, step)): msg = ( "You have put a slice in a slice. Did you forget curly braces [{...}]?" ) raise TypeError(msg) slices = list[_core.algorithm.reduce_command]() integrations = set[int]() start_int, stop_int = self.axes[i]._process_loc(start, stop) groups = [] new_axis = None if start is None and stop is None and step is None: return reduced, slices, integrations merge = 1 match step: case x if x is sum: # https://github.com/oracle/graalpython/issues/620 integrations.add(i) if start is not None or stop is not None: slices.append( _core.algorithm.slice( i, start_int, stop_int, _core.algorithm.slice_mode.crop ) ) return reduced, slices, integrations case None: pass case object(factor=x) if x is not None: merge = x case object(axis_mapping=x) if (tmp_both := x(self.axes[i])) is not None: groups, new_axis = tmp_both case object(group_mapping=x) if (tmp_groups := x(self.axes[i])) is not None: groups = tmp_groups case x if callable(x): raise NotImplementedError case _: msg = "The third argument to a slice must be rebin or projection" raise IndexError(msg) assert isinstance(start_int, int) assert isinstance(stop_int, int) # rebinning with factor if len(groups) == 0: slices.append( _core.algorithm.slice_and_rebin(i, start_int, stop_int, merge) ) # rebinning with groups else: reduced = self._rebin_with_groups( reduced or self._hist, i, groups, new_axis ) return reduced, slices, integrations def _rebin_with_groups( self, reduced: CppHistogram, i: int, groups: list[int], new_axis: Any ) -> CppHistogram: """Handle rebinning with groups.""" axes = [reduced.axis(x) for x in range(reduced.rank())] reduced_view = reduced.view(flow=True) new_axes_indices = [axes[i].edges[0]] j = 0 for group in groups: new_axes_indices += [axes[i].edges[j + group]] j += group if new_axis is None: new_axis = Variable( new_axes_indices, __dict__=axes[i].raw_metadata, underflow=axes[i].traits_underflow, overflow=axes[i].traits_overflow, ) old_axis = axes[i] axes[i] = new_axis._ax logger.debug("Axes: %s", axes) new_reduced: _core.hist._BaseHistogram | _core.hist.any_multi_cell new_reduced = reduced.__class__(axes) new_view = new_reduced.view(flow=True) j = 0 new_j_base = 0 if old_axis.traits_underflow and axes[i].traits_underflow: groups = [1, *groups] elif axes[i].traits_underflow: new_j_base = 1 if old_axis.traits_overflow and axes[i].traits_overflow: groups.append(1) for new_j, group in enumerate(groups): for _ in range(group): _combine_group_contents( new_view, reduced_view, i, j, new_j + new_j_base ) j += 1 if ( old_axis.traits_underflow and not axes[i].traits_ordered and axes[i].traits_overflow ): _combine_group_contents(new_view, reduced_view, i, 0, -1) return new_reduced def __setitem__(self, index: IndexingExpr, value: ArrayLike | Accumulator) -> None: """ There are several supported possibilities: h[slice] = array # same size If an array is given to a compatible slice, it is set. h[a:] = array # One larger If an array is given that does not match, if it does match the with-overflow size, it fills that. h[a:] = h2 If another histogram is given, that must either match with or without overflow, where the overflow bins must be overflow bins (that is, you cannot set a histogram's flow bins from another histogram that is 2 larger). If you don't want this level of type safety, just use ``h[...] = h2.view()``. """ indexes = self._compute_commonindex(index) in_array = np.asarray(value) view: Any = self.view(flow=True) value_shape: tuple[int, ...] # Support raw arrays for accumulators, the final dimension is the constructor values if ( in_array.ndim > 0 and len(view.dtype) > 0 and len(in_array.dtype) == 0 # type: ignore[arg-type] and len(view.dtype) == in_array.shape[-1] ): value_shape = in_array.shape[:-1] value_ndim = in_array.ndim - 1 else: value_shape = in_array.shape value_ndim = in_array.ndim value_n_slice = sum(isinstance(i, slice) for i in indexes) if isinstance(self._hist, _core.hist.any_multi_cell): # MultiCell histograms have to provide the cell index as first dimension, but the cell index is not included in the histogram indexing. # Slicing over the cell index is not possible for __setitem__ and is always represented as a full slice (as slice(None, None, None)). # Therefore, the number of slices is always one large than the indexed number of slices in the MultiCell case. value_n_slice += 1 # NumPy does not broadcast partial slices, but we would need # to allow it (because we do allow broadcasting up dimensions) # Instead, we simply require matching dimensions. if value_ndim > 0 and value_ndim != value_n_slice: if isinstance(self._hist, _core.hist.any_multi_cell): msg = f"Setting a {len(indexes)}D MultiCell histogram with a {value_ndim}D array must have a one higher dimension of array than histogram" else: msg = f"Setting a {len(indexes)}D histogram with a {value_ndim}D array must have a matching number of dimensions" raise ValueError(msg) # Here, value_n does not increment with n if this is not a slice value_n = 0 if isinstance(self._hist, _core.hist.any_multi_cell): # Ignore first dimension for MultiCell arrays, the first dimension is for the cells, the normal histogram axis indexing starts with dimension 2 in this case value_n = 1 # value_hist_axis tracks the axis index in the value histogram (always 0-based, # independent of the MultiCell offset in value_n) value_hist_axis = 0 for n, request in enumerate(indexes): has_underflow = self.axes[n].traits.underflow has_overflow = self.axes[n].traits.overflow if isinstance(request, slice): # This ensures that callable start/stop are handled start, stop = self.axes[n]._process_loc(request.start, request.stop) # Only consider underflow/overflow if the endpoints are not given use_underflow = has_underflow and start < 0 use_overflow = has_overflow and stop > len(self.axes[n]) # If the input is a histogram, we need to exactly match underflow/overflow if isinstance(value, Histogram): in_underflow = value.axes[value_hist_axis].traits.underflow in_overflow = value.axes[value_hist_axis].traits.overflow if use_underflow != in_underflow or use_overflow != in_overflow: msg = ( f"Cannot set histogram with underflow={in_underflow} and overflow={in_overflow} " f"to a histogram slice with underflow={use_underflow} and overflow={use_overflow}" ) raise ValueError(msg) # Convert to non-flow coordinates start_real = start + 1 if has_underflow else start stop_real = stop + 1 if has_underflow else stop # This is the total requested length without flow bins request_len = min(stop, len(self.axes[n])) - max(start, 0) # If set to a scalar, then treat it like broadcasting without flow bins # Normal requests here too # Also single element broadcasting if ( value_ndim == 0 or request_len == value_shape[value_n] or value_shape[value_n] == 1 ): start_real += 1 if start < 0 else 0 stop_real -= 1 if stop > len(self.axes[n]) else 0 # Expanded setting elif request_len + use_underflow + use_overflow == value_shape[value_n]: pass else: msg = f"Mismatched shapes {value_shape} in dimension {n}" msg += f", {value_shape[n]} != {request_len}" if use_underflow or use_overflow: msg += f" or {request_len + use_underflow + use_overflow}" raise ValueError(msg) logger.debug( "__setitem__: axis %i, start: %i (actual %i), stop: %i (actual %i)", n, start, start_real, stop, stop_real, ) indexes[n] = slice(start_real, stop_real, request.step) value_n += 1 value_hist_axis += 1 else: indexes[n] = request + has_underflow if isinstance(self._hist, _core.hist.any_multi_cell): # View of multi cell histograms has as first (index 0) dimension the cell index # Add a full slice to the beginning of the slicing expression to adept for this cell index # e.g. a slice like [0, :, 3] is converted to [:, 0, :, 3] indexes.insert(0, slice(None, None, None)) view[tuple(indexes)] = in_array def project(self, *args: int) -> Self: """ Project to a single axis or several axes on a multidimensional histogram. Provided a list of axis numbers, this will produce the histogram over those axes only. Flow bins are used if available. """ for arg in args: if arg < 0 or arg >= self.ndim: raise ValueError( f"Projection axis must be a valid axis number 0 to {self.ndim - 1}, not {arg}" ) return self._new_hist(self._hist.project(*args)) # Implementation of PlottableHistogram @property def kind(self) -> Kind: """ Returns Kind.COUNT if this is a normal summing histogram, and Kind.MEAN if this is a mean histogram. :return: Kind """ mean = self._hist._storage_type in { _core.storage.mean, _core.storage.weighted_mean, } return Kind.MEAN if mean else Kind.COUNT @typing.overload def values( self: Histogram[bhs.Int64] | Histogram[bhs.AtomicInt64], flow: bool = ... ) -> np.typing.NDArray[np.int64]: ... @typing.overload def values( self: Histogram[bhs.Double] | Histogram[bhs.Unlimited] | Histogram[bhs.Weight] | Histogram[bhs.Mean] | Histogram[bhs.WeightedMean] | Histogram[bhs.MultiCell], flow: bool = ..., ) -> np.typing.NDArray[np.float64]: ... @typing.overload def values( self, flow: bool = ... ) -> np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64]: ... def values( self, flow: bool = False ) -> np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64]: """ Returns the accumulated values. The counts for simple histograms, the sum of weights for weighted histograms, the mean for profiles, etc. If counts is equal to 0, the value in that cell is undefined if kind == "MEAN". :param flow: Enable flow bins. Not part of PlottableHistogram, but included for consistency with other methods and flexibility. :return: "np.typing.NDArray[Any]"[np.float64] """ view: Any = self.view(flow) # TODO: Might be a NumPy typing bug if len(view.dtype) == 0: return view # type: ignore[no-any-return] return view.value # type: ignore[no-any-return] @typing.overload def variances( self: Histogram[bhs.AtomicInt64] | Histogram[bhs.Int64], flow: bool = ... ) -> np.typing.NDArray[np.int64] | None: ... @typing.overload def variances( self: Histogram[bhs.Double] | Histogram[bhs.Unlimited] | Histogram[bhs.MultiCell], flow: bool = ..., ) -> np.typing.NDArray[np.float64] | None: ... @typing.overload def variances( self: Histogram[bhs.Weight] | Histogram[bhs.Mean] | Histogram[bhs.WeightedMean], flow: bool = ..., ) -> np.typing.NDArray[np.float64]: ... @typing.overload def variances( self, flow: bool = False ) -> np.typing.NDArray[np.int64] | np.typing.NDArray[np.float64] | None: ... def variances( self, flow: bool = False ) -> np.typing.NDArray[np.int64] | np.typing.NDArray[np.float64] | None: """ Returns the estimated variance of the accumulated values. The sum of squared weights for weighted histograms, the variance of samples for profiles, etc. For an unweighed histogram where kind == "COUNT", this should return the same as values if the histogram was not filled with weights, and None otherwise. If counts is equal to 1 or less, the variance in that cell is undefined if kind == "MEAN". This must be written <= 1, and not < 2; when this effective counts (weighed mean), then counts could be less than 2 but more than 1. If kind == "MEAN", the counts can be used to compute the error on the mean as sqrt(variances / counts), this works whether or not the entries are weighted if the weight variance was tracked by the implementation. Currently, this always returns - but in the future, it will return None if a weighted fill is made on a unweighed storage. :param flow: Enable flow bins. Not part of PlottableHistogram, but included for consistency with other methods and flexibility. :return: "np.typing.NDArray[Any]"[np.float64] """ view: Any = self.view(flow) if len(view.dtype) == 0: return view if self._variance_known else None if hasattr(view, "sum_of_weights"): valid = view.sum_of_weights**2 > view.sum_of_weights_squared return np.divide( # type: ignore[no-any-return] view.variance, view.sum_of_weights, out=np.full(view.sum_of_weights.shape, np.nan), where=valid, ) if hasattr(view, "count"): return np.divide( # type: ignore[no-any-return] view.variance, view.count, out=np.full(view.count.shape, np.nan), where=view.count > 1, ) return view.variance # type: ignore[no-any-return] @typing.overload def counts( self: Histogram[bhs.Int64] | Histogram[bhs.AtomicInt64], flow: bool = ... ) -> np.typing.NDArray[np.int64]: ... @typing.overload def counts( self: Histogram[bhs.Double] | Histogram[bhs.Unlimited] | Histogram[bhs.Weight] | Histogram[bhs.Mean] | Histogram[bhs.WeightedMean] | Histogram[bhs.MultiCell], flow: bool = ..., ) -> np.typing.NDArray[np.float64]: ... @typing.overload def counts( self, flow: bool = ... ) -> np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64]: ... def counts( self, flow: bool = False ) -> np.typing.NDArray[np.float64] | np.typing.NDArray[np.int64]: """ Returns the number of entries in each bin for an unweighted histogram or profile and an effective number of entries (defined below) for a weighted histogram or profile. An exotic generalized histogram could have no sensible .counts, so this is Optional and should be checked by Consumers. If kind == "MEAN", counts (effective or not) can and should be used to determine whether the mean value and its variance should be displayed (see documentation of values and variances, respectively). The counts should also be used to compute the error on the mean (see documentation of variances). For a weighted histogram, counts is defined as sum_of_weights ** 2 / sum_of_weights_squared. It is equal or less than the number of times the bin was filled, the equality holds when all filled weights are equal. The larger the spread in weights, the smaller it is, but it is always 0 if filled 0 times, and 1 if filled once, and more than 1 otherwise. :return: "np.typing.NDArray[Any]"[np.float64] """ view: Any = self.view(flow) if len(view.dtype) == 0: return view # type: ignore[no-any-return] if hasattr(view, "sum_of_weights"): return np.divide( # type: ignore[no-any-return] view.sum_of_weights**2, view.sum_of_weights_squared, out=np.zeros_like(view.sum_of_weights, dtype=np.float64), where=view.sum_of_weights_squared != 0, ) if hasattr(view, "count"): return view.count # type: ignore[no-any-return] return view.value # type: ignore[no-any-return] if TYPE_CHECKING: from uhi.typing.plottable import PlottableHistogram _: PlottableHistogram = typing.cast(Histogram[Any], None) scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/numpy.py000066400000000000000000000141601516627434300250450ustar00rootroot00000000000000from __future__ import annotations import contextlib import typing from collections.abc import Sequence from functools import reduce from operator import mul from typing import Any if typing.TYPE_CHECKING: from numpy.typing import ArrayLike else: ArrayLike = object import numpy as np from . import axis as _axis from . import storage as _storage from .histogram import Histogram __all__ = ("histogram", "histogram2d", "histogramdd") # pylint: disable=redefined-builtin # pylint: disable=redefined-outer-name def __dir__() -> list[str]: return list(__all__) def histogramdd( a: tuple[ArrayLike, ...], bins: int | tuple[int, ...] | tuple[np.typing.NDArray[Any], ...] = 10, range: None | (Sequence[None | tuple[float, float]]) = None, normed: None = None, weights: ArrayLike | None = None, density: bool = False, *, histogram: None | (type[Histogram[Any]]) = None, storage: _storage.Storage = _storage.Double(), # noqa: B008 threads: int | None = None, ) -> Any: cls: type[Histogram[Any]] = Histogram if histogram is None else histogram if normed is not None: raise KeyError( "normed=True is not recommended for use in NumPy, and is not supported in boost-histogram; use density=True instead" ) if density and histogram is not None: raise KeyError( "boost-histogram does not support the density keyword when returning a boost-histogram object" ) # Odd NumPy design here. Oh well. if isinstance(a, np.ndarray): # type: ignore[unreachable] a = a.T # type: ignore[unreachable] rank = len(a) # Integer bins: all the same with contextlib.suppress(TypeError): bins = (int(bins),) * rank # type: ignore[arg-type] assert not isinstance(bins, int) # Single None -> list of Nones if range is None: range = (None,) * rank axs: list[_axis.Axis] = [] for n, (b, r) in enumerate(zip(bins, range, strict=False)): if np.issubdtype(type(b), np.integer): if r is None: # Nextafter may affect bin edges slightly r = (np.amin(a[n]), np.amax(a[n])) # noqa: PLW2901 if r[0] == r[1]: r = (r[0] - 0.5, r[1] + 0.5) # noqa: PLW2901 new_ax = _axis.Regular( typing.cast(int, b), r[0], r[1], underflow=False, overflow=False ) axs.append(new_ax) else: barr: np.typing.NDArray[Any] = np.asarray(b, dtype=np.double) # This does have a .max member, not sure why pylint doesn't like it # pylint: disable-next=no-member barr[-1] = np.nextafter(barr[-1], np.finfo("d").max) axs.append(_axis.Variable(barr)) hist = cls(*axs, storage=storage).fill(*a, weight=weights, threads=threads) if density: areas = reduce(mul, hist.axes.widths) density_val = hist.values() / np.sum(hist.values()) / areas return (density_val, hist.to_numpy()[1:]) # Note: this is view=True since users have to ask explicitly for special # storages, so view=False would throw away part of what they are asking # for. Users can use a histogram return type if they need view=False. return hist if histogram is not None else hist.to_numpy(view=True, dd=True) def histogram2d( x: ArrayLike, y: ArrayLike, bins: int | tuple[int, int] = 10, range: None | (Sequence[None | tuple[float, float]]) = None, normed: None = None, weights: ArrayLike | None = None, density: bool = False, *, histogram: None | (type[Histogram[Any]]) = None, storage: _storage.Storage = _storage.Double(), # noqa: B008 threads: int | None = None, ) -> Any: result = histogramdd( (x, y), bins, range, normed, weights, density, histogram=histogram, storage=storage, threads=threads, ) if not isinstance(result, tuple): return result data, (edgesx, edgesy) = result return data, edgesx, edgesy def histogram( a: ArrayLike, bins: int | str | np.typing.NDArray[Any] = 10, range: tuple[float, float] | None = None, normed: None = None, weights: ArrayLike | None = None, density: bool = False, *, histogram: None | (type[Histogram[Any]]) = None, storage: _storage.Storage | None = None, threads: int | None = None, ) -> Any: # numpy 1d histogram returns integers in some cases if storage is None: storage = ( _storage.Double() if weights is not None or normed or density # type: ignore[redundant-expr] else _storage.Int64() ) if isinstance(bins, str): # Bug in NumPy 1.20 typing support - __version__ is missing if tuple(int(x) for x in np.version.version.split(".")[:2]) < (1, 13): raise KeyError( "Upgrade numpy to 1.13+ to use string arguments to boost-histogram's histogram function" ) bins = np.histogram_bin_edges(a, bins, range, weights) # TODO: make sure all types work at runtime (type ignore below) # I think it's safe and the union is in the wrong place result = histogramdd( (a,), (bins,), # type: ignore[arg-type] (range,), normed, weights, density, histogram=histogram, storage=storage, threads=threads, ) if not isinstance(result, tuple): return result data, (edges,) = result return data, edges # Process docstrings # TODO: make this a decorator for f, np_f in zip( (histogram, histogram2d, histogramdd), (np.histogram, np.histogram2d, np.histogramdd), strict=False, ): H = """\ Return a boost-histogram object using the same arguments as numpy's {}. This does not support the deprecated normed=True argument. Three extra arguments are added: histogram=bh.Histogram will enable object based output, storage=bh.storage.* lets you set the storage used, and threads=int lets you set the number of threads to fill with (0 for auto, None for 1). """ f.__doc__ = H.format(np_f.__name__) + (np_f.__doc__ or "") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/py.typed000066400000000000000000000000001516627434300250060ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/serialization/000077500000000000000000000000001516627434300261765ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/serialization/__init__.py000066400000000000000000000113741516627434300303150ustar00rootroot00000000000000from __future__ import annotations import copy from typing import Any, TypeVar import numpy as np # pylint: disable-next=import-error from .. import histogram, storage, version from ._axis import _axis_from_dict, _axis_to_dict from ._common import serialize_metadata from ._storage import ( _data_from_dict, _storage_from_dict, _storage_to_dict, _storage_type_to_str, ) __all__ = ["from_uhi", "remove_writer_info", "to_uhi"] def __dir__() -> list[str]: return __all__ def _storage_has_data_keys(storage_data: dict[str, Any], storage_type: str) -> bool: """ Check if storage data dict has the required keys for the given storage type. Returns True if all required data keys are present, False if it's structure-only. Raises ValueError if required keys are missing (malformed/partial data). """ match storage_type: case "int" | "double": required_keys = {"values"} case "weighted": required_keys = {"values", "variances"} case "mean": required_keys = {"counts", "values", "variances"} case "weighted_mean": required_keys = { "sum_of_weights", "sum_of_weights_squared", "values", "variances", } case _: msg = f"Unknown storage type: {storage_type}" raise ValueError(msg) present_keys = required_keys & set(storage_data.keys()) if not present_keys: return False if present_keys != required_keys: missing = required_keys - present_keys msg = f"{storage_type.capitalize()} storage missing required keys: {missing}" raise ValueError(msg) return True def to_uhi( h: histogram.Histogram[Any], /, *, keep_storage: bool = True ) -> dict[str, Any]: """Convert an Histogram to a dictionary.""" # Convert the histogram to a dictionary writer_info = {"boost-histogram": {"version": version.version}} # Store storage type info for AtomicInt64 and Unlimited (they serialize as int/double) storage_type_str = _storage_type_to_str(h.storage_type()) if isinstance(h.storage_type(), (storage.AtomicInt64, storage.Unlimited)): writer_info["boost-histogram"]["storage_type"] = type(h.storage_type()).__name__ data = { "uhi_schema": 1, "writer_info": writer_info, "axes": [_axis_to_dict(axis) for axis in h.axes], } if keep_storage: data["storage"] = _storage_to_dict(h.storage_type(), h.view(flow=True)) else: data["storage"] = {"type": storage_type_str} data["metadata"] = serialize_metadata(h.__dict__) return data def from_uhi(data: dict[str, Any], /) -> histogram.Histogram[Any]: """Convert a dictionary to an Histogram.""" # One time use axis = (_axis_from_dict(ax) for ax in data["axes"]) storage_data = data["storage"] storage_ = _storage_from_dict(storage_data, data.get("writer_info", {})) h = histogram.Histogram[Any](*axis, storage=storage_) h.__dict__ = data.get("metadata", {}) # Check if storage has data (if not, it's a structure-only histogram) # Validate required keys per storage type before deciding to skip data loading storage_type = storage_data["type"] has_data_keys = _storage_has_data_keys(storage_data, storage_type) if not has_data_keys: return h raw_data = _data_from_dict(storage_data) view_shape = h.view(flow=True).shape # Reshape raw_data to the expected shape. This is necessary because JSON # serialization can collapse empty dimensions (e.g. (5, 0, 0) -> (5, 0)), # so we must restore the correct number of dimensions. storage_type = storage_data["type"] if storage_type in {"weighted", "mean", "weighted_mean"}: raw_data = np.asarray(raw_data) raw_data = raw_data.reshape(view_shape + raw_data.shape[-1:]) else: raw_data = np.reshape(raw_data, view_shape) h[...] = raw_data return h T = TypeVar("T", bound="dict[str, Any]") def remove_writer_info(obj: T, /, *, library: str | None = "boost-histogram") -> T: """ Removes all ``writer_info`` for a library from a histogram dict, axes dict, or storage dict. Makes copies where required, and the outer dictionary is always copied. Specify a library name, or ``None`` to remove all. """ obj = copy.copy(obj) if library is None: obj.pop("writer_info", None) elif library in obj.get("writer_info", {}): obj["writer_info"] = copy.copy(obj["writer_info"]) del obj["writer_info"][library] if "axes" in obj: obj["axes"] = [remove_writer_info(ax, library=library) for ax in obj["axes"]] if "storage" in obj: obj["storage"] = remove_writer_info(obj["storage"], library=library) return obj scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/serialization/_axis.py000066400000000000000000000125561516627434300276640ustar00rootroot00000000000000from __future__ import annotations import functools from typing import Any from .. import axis from ._common import serialize_metadata __all__ = ["_axis_from_dict", "_axis_to_dict"] def __dir__() -> list[str]: return __all__ @functools.singledispatch def _axis_to_dict(ax: Any, /) -> dict[str, Any]: """Convert an axis to a dictionary.""" raise TypeError(f"Unsupported axis type: {type(ax)}") @_axis_to_dict.register(axis.Regular) @_axis_to_dict.register(axis.Integer) def _(ax: axis.Regular | axis.Integer, /) -> dict[str, Any]: """Convert a Regular axis to a dictionary.""" shared = { "underflow": ax.traits.underflow, "overflow": ax.traits.overflow, "circular": ax.traits.circular, } # Special handling if the axis has a transform if isinstance(ax, axis.Regular) and ax.transform is not None: data = { "type": "variable", "edges": ax.edges, **shared, } elif isinstance(ax, axis.Integer): data = { "type": "regular", "lower": int(ax.edges[0]), "upper": int(ax.edges[-1]), "bins": ax.size, **shared, } else: data = { "type": "regular", "lower": float(ax.edges[0]), "upper": float(ax.edges[-1]), "bins": ax.size, **shared, } writer_info = dict[str, str | bool]() if isinstance(ax, axis.Integer): writer_info["orig_type"] = "Integer" if ax.traits.growth: writer_info["growth"] = True if writer_info: data["writer_info"] = {"boost-histogram": writer_info} metadata = serialize_metadata(ax.__dict__) if metadata: data["metadata"] = metadata return data @_axis_to_dict.register def _(ax: axis.Variable, /) -> dict[str, Any]: """Convert a Variable or Integer axis to a dictionary.""" data = { "type": "variable", "edges": ax.edges, "underflow": ax.traits.underflow, "overflow": ax.traits.overflow, "circular": ax.traits.circular, } if ax.traits.growth: data["writer_info"] = {"boost-histogram": {"growth": True}} metadata = serialize_metadata(ax.__dict__) if metadata: data["metadata"] = metadata return data @_axis_to_dict.register def _(ax: axis.IntCategory, /) -> dict[str, Any]: """Convert an IntCategory axis to a dictionary.""" data = { "type": "category_int", "categories": list(ax), "flow": ax.traits.overflow, } if ax.traits.growth: data["writer_info"] = {"boost-histogram": {"growth": True}} metadata = serialize_metadata(ax.__dict__) if metadata: data["metadata"] = metadata return data @_axis_to_dict.register def _(ax: axis.StrCategory, /) -> dict[str, Any]: """Convert a StrCategory axis to a dictionary.""" data = { "type": "category_str", "categories": list(ax), "flow": ax.traits.overflow, } if ax.traits.growth: data["writer_info"] = {"boost-histogram": {"growth": True}} metadata = serialize_metadata(ax.__dict__) if metadata: data["metadata"] = metadata return data @_axis_to_dict.register def _(ax: axis.Boolean, /) -> dict[str, Any]: """Convert a Boolean axis to a dictionary.""" data: dict[str, Any] = { "type": "boolean", } metadata = serialize_metadata(ax.__dict__) if metadata: data["metadata"] = metadata return data def _axis_from_dict(data: dict[str, Any], /) -> axis.Axis: writer_info = data.get("writer_info", {}).get("boost-histogram", {}) orig_type = writer_info.get("orig_type", "") if orig_type == "Integer": assert data["upper"] - data["lower"] == data["bins"] return axis.Integer( data["lower"], data["upper"], underflow=data["underflow"], overflow=data["overflow"], circular=data["circular"], growth=writer_info.get("growth", False), __dict__=data.get("metadata"), ) hist_type = data["type"] if hist_type == "regular": return axis.Regular( data["bins"], data["lower"], data["upper"], underflow=data["underflow"], overflow=data["overflow"], circular=data["circular"], growth=writer_info.get("growth", False), __dict__=data.get("metadata"), ) if hist_type == "variable": return axis.Variable( data["edges"], underflow=data["underflow"], overflow=data["overflow"], circular=data["circular"], growth=writer_info.get("growth", False), __dict__=data.get("metadata"), ) if hist_type == "category_int": return axis.IntCategory( data["categories"], overflow=data["flow"], growth=writer_info.get("growth", False), __dict__=data.get("metadata"), ) if hist_type == "category_str": return axis.StrCategory( data["categories"], overflow=data["flow"], growth=writer_info.get("growth", False), __dict__=data.get("metadata"), ) if hist_type == "boolean": return axis.Boolean(__dict__=data.get("metadata")) raise TypeError(f"Unsupported axis type: {hist_type}") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/serialization/_common.py000066400000000000000000000003301516627434300301730ustar00rootroot00000000000000from __future__ import annotations from typing import Any def serialize_metadata(value: dict[str, Any], /) -> dict[str, Any]: return {k: v for k, v in value.items() if not k.startswith("@") and v is not None} scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/serialization/_storage.py000066400000000000000000000110161516627434300303520ustar00rootroot00000000000000from __future__ import annotations import functools from typing import Any import numpy as np from .. import storage __all__ = [ "_data_from_dict", "_storage_from_dict", "_storage_to_dict", "_storage_type_to_str", ] def __dir__() -> list[str]: return __all__ def _storage_type_to_str(_storage: storage.Storage, /) -> str: """Return the canonical storage type string for a storage object.""" match _storage: case storage.Int64(): return "int" case storage.Double(): return "double" case storage.AtomicInt64(): return "int" case storage.Unlimited(): return "double" case storage.Weight(): return "weighted" case storage.Mean(): return "mean" case storage.WeightedMean(): return "weighted_mean" case _: raise TypeError(f"Unsupported storage type: {_storage}") @functools.singledispatch def _storage_to_dict(_storage: storage.Storage, /, data: Any) -> dict[str, Any]: # noqa: ARG001 """Convert a storage to a dictionary.""" msg = f"Unsupported storage type: {_storage}" raise TypeError(msg) @_storage_to_dict.register(storage.Int64) @_storage_to_dict.register(storage.Double) def _(_storage: storage.Int64 | storage.Double, /, data: Any) -> dict[str, Any]: return {"type": _storage_type_to_str(_storage), "values": data} @_storage_to_dict.register(storage.AtomicInt64) @_storage_to_dict.register(storage.Unlimited) def _( _storage: storage.AtomicInt64 | storage.Unlimited, /, data: Any, ) -> dict[str, Any]: return { "type": "int" if np.issubdtype(data.dtype, np.integer) else "double", "values": data, } @_storage_to_dict.register(storage.Weight) def _(_storage: storage.Weight, /, data: Any) -> dict[str, Any]: return { "type": _storage_type_to_str(_storage), "values": data.value, "variances": data.variance, } @_storage_to_dict.register(storage.Mean) def _(_storage: storage.Mean, /, data: Any) -> dict[str, Any]: return { "type": _storage_type_to_str(_storage), "counts": data.count, "values": data.value, "variances": data.variance, } @_storage_to_dict.register(storage.WeightedMean) def _(_storage: storage.WeightedMean, /, data: Any) -> dict[str, Any]: return { "type": _storage_type_to_str(_storage), "sum_of_weights": data.sum_of_weights, "sum_of_weights_squared": data.sum_of_weights_squared, "values": data.value, "variances": data.variance, } def _storage_from_dict( data: dict[str, Any], writer_info: dict[str, Any] | None = None, / ) -> storage.Storage: """Convert a dictionary to a storage object.""" # If loading a boost-histogram, we can load the exact original type # Check both the main writer_info (new location) and storage writer_info (old location) orig_type = "" if writer_info: orig_type = writer_info.get("boost-histogram", {}).get("storage_type", "") if not orig_type: orig_type = ( data.get("writer_info", {}).get("boost-histogram", {}).get("orig_type", "") ) if orig_type == "AtomicInt64": return storage.AtomicInt64() if orig_type == "Unlimited": return storage.Unlimited() storage_type = data["type"] if storage_type == "int": return storage.Int64() if storage_type == "double": return storage.Double() if storage_type == "weighted": return storage.Weight() if storage_type == "mean": return storage.Mean() if storage_type == "weighted_mean": return storage.WeightedMean() raise TypeError(f"Unsupported storage type: {storage_type}") def _data_from_dict(data: dict[str, Any], /) -> np.typing.NDArray[Any]: """Convert a dictionary to data.""" storage_type = data["type"] if storage_type in {"int", "double"}: return data["values"] # type: ignore[no-any-return] if storage_type == "weighted": return np.stack([data["values"], data["variances"]], axis=-1) if storage_type == "mean": return np.stack([data["counts"], data["values"], data["variances"]], axis=-1) if storage_type == "weighted_mean": return np.stack( [ data["sum_of_weights"], data["sum_of_weights_squared"], data["values"], data["variances"], ], axis=-1, ) raise TypeError(f"Unsupported storage type: {storage_type}") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/storage.py000066400000000000000000000035321516627434300253420ustar00rootroot00000000000000from __future__ import annotations from typing import ClassVar import boost_histogram from ._core import accumulators # pylint: disable=no-name-in-module from ._core import storage as store # pylint: disable=no-name-in-module __all__ = [ "AtomicInt64", "Double", "Int64", "Mean", "MultiCell", "Storage", "Unlimited", "Weight", "WeightedMean", ] def __dir__() -> list[str]: return __all__ # Simple mixin to provide a common base class for types class Storage: _family: object def __init_subclass__(cls, *, family: object) -> None: super().__init_subclass__() cls._family = family def __repr__(self) -> str: return f"{self.__class__.__name__}()" accumulator: ClassVar[ ( type[ int | float | accumulators.WeightedMean | accumulators.WeightedSum | accumulators.Mean ] ) ] class Int64(store.int64, Storage, family=boost_histogram): accumulator = int class Double(store.double, Storage, family=boost_histogram): accumulator = float class AtomicInt64(store.atomic_int64, Storage, family=boost_histogram): accumulator = int class Unlimited(store.unlimited, Storage, family=boost_histogram): accumulator = float class Weight(store.weight, Storage, family=boost_histogram): accumulator = accumulators.WeightedSum class Mean(store.mean, Storage, family=boost_histogram): accumulator = accumulators.Mean class WeightedMean(store.weighted_mean, Storage, family=boost_histogram): accumulator = accumulators.WeightedMean class MultiCell(store.multi_cell, Storage, family=boost_histogram): accumulator = float __match_args__ = ("nelem",) def __repr__(self) -> str: return f"{self.__class__.__name__}({self.nelem})" scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/tag.py000066400000000000000000000136731516627434300244600ustar00rootroot00000000000000# bh.sum is just the Python sum, so from boost_histogram import * is safe (but # not recommended) from __future__ import annotations import copy from builtins import sum from collections.abc import Sequence from typing import TYPE_CHECKING, TypeVar import numpy as np if TYPE_CHECKING: from uhi.typing.plottable import PlottableAxis from ._compat.typing import Self from .typing import AxisLike __all__ = ("Locator", "Slicer", "at", "loc", "overflow", "rebin", "sum", "underflow") def Slicer() -> np.lib._index_tricks_impl.IndexExpression: """ It is encouraged to use ``np.s_`` directly instead of this function: h[{0: np.s_[::bh.rebin(2)]}] # rebin axis 0 by two This is provided for backward compatibility. """ return np.s_ T = TypeVar("T", bound="Locator") class Locator: __slots__ = ("offset",) NAME = "" def __init__(self, offset: int = 0) -> None: if not isinstance(offset, int): raise ValueError("The offset must be an integer") self.offset = offset def __add__(self, offset: int) -> Self: other = copy.copy(self) other.offset += offset return other def __sub__(self, offset: int) -> Self: other = copy.copy(self) other.offset -= offset return other def _print_self_(self) -> str: return "" def __repr__(self) -> str: s = self.NAME or self.__class__.__name__ s += self._print_self_() if self.offset != 0: s += " + " if self.offset > 0 else " - " s += str(abs(self.offset)) return s class loc(Locator): __slots__ = ("value",) def __init__(self, value: str | float, offset: int = 0) -> None: super().__init__(offset) self.value = value def _print_self_(self) -> str: return f"({self.value})" def __call__(self, axis: AxisLike) -> int: return axis.index(self.value) + self.offset class Underflow(Locator): __slots__ = () NAME = "underflow" def __call__(self, axis: AxisLike) -> int: # noqa: ARG002 return -1 + self.offset underflow = Underflow() class Overflow(Locator): __slots__ = () NAME = "overflow" def __call__(self, axis: AxisLike) -> int: return len(axis) + self.offset overflow = Overflow() class at: __slots__ = ("value",) def __init__(self, value: int) -> None: self.value = value def __call__(self, axis: AxisLike) -> int: # noqa: ARG002 return self.value class rebin: __slots__ = ( "axis", "edges", "factor", "groups", ) def __init__( self, factor_or_axis: int | PlottableAxis | None = None, /, *, factor: int | None = None, groups: Sequence[int] | None = None, edges: Sequence[int | float] | None = None, axis: PlottableAxis | None = None, ) -> None: if isinstance(factor_or_axis, int): factor = factor_or_axis elif factor_or_axis is not None: axis = factor_or_axis total_args = sum(i is not None for i in [factor, groups, edges]) if total_args != 1 and axis is None: raise ValueError("Exactly one argument should be provided") self.groups = groups self.edges = edges self.axis = axis self.factor = factor def __repr__(self) -> str: repr_str = f"{self.__class__.__name__}" args: dict[str, int | Sequence[int | float] | PlottableAxis | None] = { "factor": self.factor, "groups": self.groups, "edges": self.edges, "axis": self.axis, } for k, v in args.items(): if v is not None: return_str = f"{repr_str}({k}={v})" break return return_str # Note: this preserves the input type of `self.axis`, so is safe within a # single UHI library, but not cross-library. Returns None for the axis if # an axis is not provided, the caller should make an axis if that's the # case. def axis_mapping( self, axis: PlottableAxis ) -> tuple[Sequence[int], PlottableAxis | None]: return (self.group_mapping(axis), self.axis) def group_mapping(self, axis: PlottableAxis) -> Sequence[int]: if self.groups is not None: if sum(self.groups) != len(axis): msg = f"The sum of the groups ({sum(self.groups)}) must be equal to the number of bins in the axis ({len(axis)})" raise ValueError(msg) return self.groups if self.factor is not None: return [self.factor] * len(axis) if self.edges is not None or self.axis is not None: newedges = None if self.edges is not None: newedges = self.edges elif self.axis is not None and hasattr(self.axis, "edges"): newedges = self.axis.edges if newedges is not None and hasattr(axis, "edges"): if newedges[0] != axis.edges[0]: msg = "Edges must start at first bin" raise ValueError(msg) if newedges[-1] != axis.edges[-1]: msg = "Edges must end at last bin" raise ValueError(msg) matched_ixes = [np.abs(axis.edges - edge).argmin() for edge in newedges] missing_edges = [ edge for ix, edge in zip(matched_ixes, newedges, strict=False) if not np.isclose(axis.edges[ix], edge) ] if missing_edges: missing_edges_repr = ", ".join(map(str, missing_edges)) msg = f"Edge(s) {missing_edges_repr} not found in axis" raise ValueError(msg) return [ int(ix - matched_ixes[i]) for i, ix in enumerate(matched_ixes[1:]) ] raise ValueError("No rebinning factor or groups provided") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/test.py000066400000000000000000000054171516627434300246610ustar00rootroot00000000000000""" This test module does simple smoke checks to make sure boost_histogram compiled correctly. Run with `python -m boost_histogram.test`. """ from __future__ import annotations import pickle import unittest import boost_histogram as bh class TestBoostHistogram(unittest.TestCase): def test_1d_histogram(self) -> None: h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h.fill([0.15, 0.25, 0.25, 0.35]) self.assertEqual(h[0], 0) self.assertEqual(h[1], 1) self.assertEqual(h[2], 2) self.assertEqual(h[3], 1) self.assertEqual(h[4], 0) def test_2d_histogram(self) -> None: h = bh.Histogram( bh.axis.Integer(4, 10), bh.axis.StrCategory(["a", "b", "c"], overflow=False), storage=bh.storage.Weight(), ) h.fill([5, 5, 5, 7], ["a", "b", "b", "c"]) val = h[bh.loc(5), bh.loc("a")] assert isinstance(val, bh.accumulators.WeightedSum) self.assertEqual(val.value, 1) val = h[bh.loc(5), bh.loc("b")] assert isinstance(val, bh.accumulators.WeightedSum) self.assertEqual(val.value, 2) val = h[bh.loc(7), bh.loc("c")] assert isinstance(val, bh.accumulators.WeightedSum) self.assertEqual(val.value, 1) val = h[bh.loc(8), bh.loc("c")] assert isinstance(val, bh.accumulators.WeightedSum) self.assertEqual(val.value, 0) def test_pickle(self) -> None: h = bh.Histogram( bh.axis.Variable([0, 0.3, 0.4, 0.5, 1.0]), storage=bh.storage.Mean() ) h.fill([0.15, 0.25, 0.25, 0.35], sample=[1, 2, 3, 4]) pickled = pickle.dumps(h) unpickled = pickle.loads(pickled) self.assertEqual(h, unpickled) def test_sqrt_transform(self) -> None: a = bh.axis.Regular(10, 0, 10, transform=bh.axis.transform.sqrt) # Edges: 0. , 0.1, 0.4, 0.9, 1.6, 2.5, 3.6, 4.9, 6.4, 8.1, 10. self.assertEqual(a.index(-100), 10) # Always in overflow bin self.assertEqual(a.index(-1), 10) # When transform is invalid self.assertEqual(a.index(0), 0) self.assertEqual(a.index(0.15), 1) self.assertEqual(a.index(0.5), 2) self.assertEqual(a.index(1), 3) self.assertEqual(a.index(1.7), 4) self.assertEqual(a.index(9), 9) self.assertEqual(a.index(11), 10) self.assertEqual(a.index(1000), 10) val = a.bin(0) assert isinstance(val, tuple) self.assertAlmostEqual(val[0], 0.0) val = a.bin(1) assert isinstance(val, tuple) self.assertAlmostEqual(val[0], 0.1) val = a.bin(2) assert isinstance(val, tuple) self.assertAlmostEqual(val[0], 0.4) if __name__ == "__main__": unittest.main("boost_histogram.test", warnings="error") scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/typing.py000066400000000000000000000024611516627434300252100ustar00rootroot00000000000000from __future__ import annotations from types import EllipsisType from typing import TYPE_CHECKING, Any, Protocol, TypeAlias if TYPE_CHECKING: from collections.abc import Sequence from numpy import ufunc as Ufunc from numpy.typing import ArrayLike from uhi.typing.plottable import PlottableAxis from boost_histogram._core.accumulators import Mean, WeightedMean, WeightedSum from boost_histogram._core.hist import _BaseHistogram as CppHistogram Accumulator = WeightedSum | Mean | WeightedMean else: WeightedSum = Any Mean = Any WeightedMean = Any ArrayLike = Any Ufunc = Any CppHistogram = Any Accumulator = Any __all__ = ( "ArrayLike", "AxisLike", "CppHistogram", "Mean", "RebinProtocol", "StdIndex", "StrIndex", "Ufunc", "WeightedMean", "WeightedSum", ) class AxisLike(Protocol): def index(self, value: float | str) -> int: ... def __len__(self) -> int: ... StdIndex: TypeAlias = ( int | slice | EllipsisType | tuple[slice | int | EllipsisType, ...] ) StrIndex: TypeAlias = ( int | slice | str | EllipsisType | tuple[slice | int | str | EllipsisType, ...] ) class RebinProtocol(Protocol): def axis_mapping( self, axis: PlottableAxis ) -> tuple[Sequence[int], PlottableAxis | None]: ... scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/version.pyi000066400000000000000000000000151516627434300255250ustar00rootroot00000000000000version: str scikit-hep-boost-histogram-5f6f3e3/src/boost_histogram/view.py000066400000000000000000000234131516627434300246500ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Callable, Mapping, MutableMapping from typing import Any, ClassVar, Literal import numpy as np from .accumulators import Mean, WeightedMean, WeightedSum from .typing import ArrayLike, StrIndex, Ufunc UFMethod = Literal["__call__", "reduce", "reduceat", "accumulate", "outer", "at"] class View(np.ndarray[Any, Any]): __slots__ = () _FIELDS: ClassVar[tuple[str, ...]] _PARENT: type[WeightedSum | WeightedMean | Mean] def __getitem__(self, ind: StrIndex) -> np.typing.NDArray[Any]: # type: ignore[override] sliced = super().__getitem__(ind) # type: ignore[index] # If the dtype has changed, return a normal array (no longer a record). # This must be checked before the shape check so that 0-d field access # (e.g. ``view["value"]`` on a 0-d MeanView) returns a plain scalar # array instead of trying to iterate over a 0-d array. if sliced.dtype != self.dtype: return np.asarray(sliced) # If the shape is empty, return the parent type if not sliced.shape: return self._PARENT._make(*sliced) # type: ignore[return-value] # Otherwise, no change, return the same View type return sliced def __repr__(self) -> str: # NumPy starts the ndarray class name with "array", so we replace it # with our class name return f"{self.__class__.__name__}(\n " + repr(self.view(np.ndarray))[6:] def __str__(self) -> str: my_fields = ", ".join(self._FIELDS) return f"{self.__class__.__name__}: ({my_fields})\n{self.view(np.ndarray)}" def __setitem__(self, ind: StrIndex, value: ArrayLike) -> None: # type: ignore[override] # `.value` really is ["value"] for an record array if isinstance(ind, str): super().__setitem__(ind, value) return current_ndim = super().__getitem__(ind).ndim # type: ignore[index] array: np.typing.NDArray[Any] = np.asarray(value) msg = "Needs matching ndarray or n+1 dim array" if array.ndim == current_ndim + 1: if len(self._FIELDS) == array.shape[-1]: self.__setitem__(ind, self._PARENT._array(*np.moveaxis(array, -1, 0))) # type: ignore[assignment] return msg += f", final dimension should be {len(self._FIELDS)} for this storage, got {array.shape[-1]} instead" raise ValueError(msg) if self.dtype == array.dtype: super().__setitem__(ind, array) # type: ignore[index] return msg += f", {current_ndim}D {self.dtype} or {current_ndim + 1}D required, got {array.ndim}D {array.dtype}" raise ValueError(msg) def make_getitem_property(name: str) -> property: def fget(self: Mapping[str, Any]) -> Any: return self[name] def fset(self: MutableMapping[str, Any], value: Any) -> None: self[name] = value return property(fget, fset) def fields(*names: str) -> Callable[[type[object]], type[object]]: """ This decorator adds the name to the _FIELDS class property (for printing in reprs), and adds a property that looks like this: @property def name(self): return self["name"] @name.setter def name(self, value): self["name"] = value """ def injector(cls: type[object]) -> type[object]: if hasattr(cls, "_FIELDS"): raise RuntimeError( f"{cls.__name__} already has had a fields decorator applied" ) my_fields = [] for name in names: my_fields.append(name) setattr(cls, name, make_getitem_property(name)) cls._FIELDS = tuple(my_fields) # type: ignore[attr-defined] return cls return injector @fields("value", "variance") class WeightedSumView(View): __slots__ = () _PARENT = WeightedSum value: np.typing.NDArray[Any] variance: np.typing.NDArray[Any] # Could be implemented on master View def __array_ufunc__( self, ufunc: Ufunc, method: UFMethod, *inputs: Any, **kwargs: Any ) -> np.typing.NDArray[Any]: # Avoid infinite recursion raw_inputs = [np.asarray(x) for x in inputs] # First match the ones without a pre-computed out= parameter match method, ufunc, raw_inputs: case "reduce", np.add, [raw_input]: results = ( ufunc.reduce(raw_input[field], **kwargs) for field in self._FIELDS ) return self._PARENT._make(*results) # type: ignore[return-value] # These use a pre-computed out parameter (result,) = ( kwargs.pop("out") if "out" in kwargs else [np.empty(self.shape, self.dtype)] ) match method, ufunc, raw_inputs: # Support unary + and - case "__call__", np.negative | np.positive, [raw_input]: ufunc(raw_input["value"], out=result["value"], **kwargs) result["variance"] = raw_input["variance"] return result.view(self.__class__) # Support + and - case "__call__", np.add | np.subtract, [raw_input1, raw_input2]: if raw_input1.dtype == raw_input2.dtype: ufunc( raw_input1["value"], raw_input2["value"], out=result["value"], **kwargs, ) np.add( raw_input1["variance"], raw_input2["variance"], out=result["variance"], **kwargs, ) elif self.dtype == raw_input1.dtype: ufunc( raw_input1["value"], raw_input2, out=result["value"], **kwargs, ) np.add( raw_input1["variance"], raw_input2**2, out=result["variance"], **kwargs, ) else: ufunc( raw_input1, raw_input2["value"], out=result["value"], **kwargs, ) np.add( raw_input1**2, raw_input2["variance"], out=result["variance"], **kwargs, ) return result.view(self.__class__) case ( "__call__", np.multiply | np.divide | np.true_divide | np.floor_divide, [raw_input1, raw_input2], ): if self.dtype == raw_input1.dtype: ufunc( raw_input1["value"], raw_input2, out=result["value"], **kwargs, ) ufunc( raw_input1["variance"], raw_input2**2, out=result["variance"], **kwargs, ) else: ufunc( raw_input1, raw_input2["value"], out=result["value"], **kwargs, ) ufunc( raw_input1**2, raw_input2["variance"], out=result["variance"], **kwargs, ) return result.view(self.__class__) case "accumulate", np.add, [raw_input]: for field in self._FIELDS: ufunc.accumulate(self[field], out=result[field], **kwargs) return result.view(self.__class__) # If unsupported, just pass through (will return not implemented) # pylint: disable-next=no-member return super().__array_ufunc__(ufunc, method, *raw_inputs, **kwargs) # type: ignore[no-any-return] @fields( "sum_of_weights", "sum_of_weights_squared", "value", "_sum_of_weighted_deltas_squared", ) class WeightedMeanView(View): __slots__ = () _PARENT = WeightedMean sum_of_weights: np.typing.NDArray[Any] sum_of_weights_squared: np.typing.NDArray[Any] value: np.typing.NDArray[Any] _sum_of_weighted_deltas_squared: np.typing.NDArray[Any] @property def variance(self) -> np.typing.NDArray[Any]: with np.errstate(divide="ignore", invalid="ignore"): return self["_sum_of_weighted_deltas_squared"] / ( # type: ignore[no-any-return] self["sum_of_weights"] - self["sum_of_weights_squared"] / self["sum_of_weights"] ) @fields("count", "value", "_sum_of_deltas_squared") class MeanView(View): __slots__ = () _PARENT = Mean count: np.typing.NDArray[Any] value: np.typing.NDArray[Any] _sum_of_deltas_squared: np.typing.NDArray[Any] # Variance is a computation @property def variance(self) -> np.typing.NDArray[Any]: with np.errstate(divide="ignore", invalid="ignore"): return self["_sum_of_deltas_squared"] / (self["count"] - 1) # type: ignore[no-any-return] def _to_view( item: np.typing.NDArray[Any], value: bool = False ) -> np.typing.NDArray[Any] | WeightedSumView | WeightedMeanView | MeanView: for cls in View.__subclasses__(): if item.dtype.names == cls._FIELDS: ret = item.view(cls) if value and ret.shape: return ret.value # type: ignore[no-any-return, attr-defined] return ret return item scikit-hep-boost-histogram-5f6f3e3/src/module.cpp000066400000000000000000000020221516627434300221030ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include void register_algorithms(py::module&); void register_storages(py::module&); void register_axes(py::module&); void register_histograms(py::module&); void register_accumulators(py::module&); void register_transforms(py::module&); PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { py::module storage = m.def_submodule("storage"); register_storages(storage); py::module ax = m.def_submodule("axis"); register_axes(ax); py::module trans = ax.def_submodule("transform"); register_transforms(trans); py::module hist = m.def_submodule("hist"); register_histograms(hist); py::module accumulators = m.def_submodule("accumulators"); register_accumulators(accumulators); py::module algorithm = m.def_submodule("algorithm"); register_algorithms(algorithm); } scikit-hep-boost-histogram-5f6f3e3/src/register_accumulators.cpp000066400000000000000000000313241516627434300252330ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include #include #include #include #include #include #include #include namespace { /// The mean fill can be implemented once. (sum fill varies slightly) template decltype(auto) make_mean_fill() { return [](T& self, const py::object& value, const py::object& weight) { if(weight.is_none()) { py::vectorize([](T& self, double val) { self(val); })(self, value); } else { py::vectorize([](T& self, double wei, double val) { self(bh::weight(wei), val); })(self, weight, value); } return self; }; } /// The mean call can be implemented once. (sum uses +=) template decltype(auto) make_mean_call() { return [](T& self, double value, const py::object& weight) { if(weight.is_none()) self(value); else self(bh::weight(py::cast(weight)), value); return self; }; } template decltype(auto) make_buffer() { return [](T& self) -> py::buffer_info { return py::buffer_info( &self, // Pointer to buffer sizeof(T), // Size of one scalar py::format_descriptor::format(), // Format registered with // PYBIND11_NUMPY_DTYPE 0, // Number of dimensions {}, // Buffer dimensions {} // Stride ); }; } } // namespace // NOLINTNEXTLINE(misc-use-internal-linkage) void register_accumulators(py::module& accumulators) { // Naming convention: // If a value is publicly available in Boost.Histogram accumulators // as a method, it has the same name in the numpy record array. // If it is not available except through a computation, it has // the same name as the private property without the trailing _. using weighted_sum = accumulators::weighted_sum; PYBIND11_NUMPY_DTYPE(weighted_sum, value, variance); register_accumulator( accumulators, "WeightedSum", py::buffer_protocol()) .def_buffer(make_buffer()) .def(py::init(), "value"_a) .def(py::init(), "value"_a, "variance"_a) .def_readonly("value", &weighted_sum::value) .def_readonly("variance", &weighted_sum::variance) .def("__iadd__", [](weighted_sum& self, double value) { self += bh::weight(value); return self; }) .def( "fill", [](weighted_sum& self, const py::object& value, const py::object& variance) { if(variance.is_none()) { py::vectorize([](weighted_sum& self, double val) { self += bh::weight(val); })(self, value); } else { py::vectorize([](weighted_sum& self, double val, double var) { self += weighted_sum(val, var); })(self, value, variance); } return self; }, "value"_a, py::kw_only(), "variance"_a = py::none(), "Fill the accumulator with values. Optional variance parameter.") // This adapts existing memory to an accumulator .def_static("_make", py::vectorize([](const double& a, const double& b) { return weighted_sum(a, b); })) // This creates a array of accumulators using the normal constructor arguments .def_static("_array", py::vectorize([](const double& a, const double& b) { return weighted_sum(a, b); })) .def("__getitem__", [](const weighted_sum& self, const py::str& key) { if(key.equal(py::str("value"))) return self.value; if(key.equal(py::str("variance"))) return self.variance; throw py::key_error( py::str("{0} not one of value, variance").format(key)); }) .def("__setitem__", [](weighted_sum& self, const py::str& key, double value) { if(key.equal(py::str("value"))) self.value = value; else if(key.equal(py::str("variance"))) self.variance = value; else throw py::key_error( py::str("{0} not one of value, variance").format(key)); }) .def("_ipython_key_completions_", [](const py::object& /* self */) { return py::make_tuple("value", "variance"); }) ; using sum = bh::accumulators::sum; register_accumulator(accumulators, "Sum") .def(py::init(), "value"_a) .def_property_readonly("value", &sum::value) .def(py::self += double()) .def( "fill", [](sum& self, const py::object& value) { py::vectorize([](sum& self, double v) { self += v; })(self, value); return self; }, "value"_a, "Run over an array with the accumulator") .def_property_readonly("_small", &sum::small_part) .def_property_readonly("_large", &sum::large_part) ; using weighted_mean = accumulators::weighted_mean; PYBIND11_NUMPY_DTYPE(weighted_mean, sum_of_weights, sum_of_weights_squared, value, _sum_of_weighted_deltas_squared); register_accumulator( accumulators, "WeightedMean", py::buffer_protocol()) .def_buffer(make_buffer()) .def(py::init(), "sum_of_weights"_a, "sum_of_weights_squared"_a, "value"_a, "variance"_a) .def_readonly("sum_of_weights", &weighted_mean::sum_of_weights) .def_readonly("sum_of_weights_squared", &weighted_mean::sum_of_weights_squared) .def_readonly("value", &weighted_mean::value) .def_readonly("_sum_of_weighted_deltas_squared", &weighted_mean::_sum_of_weighted_deltas_squared) .def_property_readonly("variance", &weighted_mean::variance) .def("__call__", make_mean_call(), "value"_a, py::kw_only(), "weight"_a = py::none(), "Fill with value and optional keyword-only weight") .def("fill", make_mean_fill(), "value"_a, py::kw_only(), "weight"_a = py::none(), "Fill the accumulator with values. Optional weight parameter.") .def_static( "_make", py::vectorize( [](const double& a, const double& b, const double& c, const double& d) { return weighted_mean(a, b, c, d, true); })) .def_static( "_array", py::vectorize( [](const double& a, const double& b, const double& c, const double& d) { return weighted_mean(a, b, c, d); })) .def("__getitem__", [](const weighted_mean& self, const py::str& key) { if(key.equal(py::str("value"))) return self.value; if(key.equal(py::str("sum_of_weights"))) return self.sum_of_weights; if(key.equal(py::str("sum_of_weights_squared"))) return self.sum_of_weights_squared; if(key.equal(py::str("_sum_of_weighted_deltas_squared"))) return self._sum_of_weighted_deltas_squared; throw py::key_error( py::str("{0} not one of value, sum_of_weights, " "sum_of_weights_squared, _sum_of_weighted_deltas_squared") .format(key)); }) .def("__setitem__", [](weighted_mean& self, const py::str& key, double value) { if(key.equal(py::str("value"))) self.value = value; else if(key.equal(py::str("sum_of_weights"))) self.sum_of_weights = value; else if(key.equal(py::str("sum_of_weights_squared"))) self.sum_of_weights_squared = value; else if(key.equal(py::str("_sum_of_weighted_deltas_squared"))) self._sum_of_weighted_deltas_squared = value; else throw py::key_error( py::str( "{0} not one of value, sum_of_weights, " "sum_of_weights_squared, _sum_of_weighted_deltas_squared") .format(key)); }) .def("_ipython_key_completions_", [](const py::object& /* self */) { return py::make_tuple("value", "sum_of_weights", "sum_of_weights_squared", "_sum_of_weighted_deltas_squared"); }) ; using mean = accumulators::mean; PYBIND11_NUMPY_DTYPE(mean, count, value, _sum_of_deltas_squared); register_accumulator(accumulators, "Mean", py::buffer_protocol()) .def_buffer(make_buffer()) .def(py::init(), "count"_a, "value"_a, "variance"_a) .def_readonly("count", &mean::count) .def_readonly("value", &mean::value) .def_readonly("_sum_of_deltas_squared", &mean::_sum_of_deltas_squared) .def_property_readonly("variance", &mean::variance) .def("__call__", make_mean_call(), "value"_a, py::kw_only(), "weight"_a = py::none(), "Fill with value and optional keyword-only weight") .def("fill", make_mean_fill(), "value"_a, py::kw_only(), "weight"_a = py::none(), "Fill the accumulator with values. Optional weight parameter.") .def_static( "_make", py::vectorize([](const double& a, const double& b, const double& c) { return mean(a, b, c, true); })) .def_static( "_array", py::vectorize([](const double& a, const double& b, const double& c) { return mean(a, b, c); })) .def("__getitem__", [](const mean& self, const py::str& key) { if(key.equal(py::str("count"))) return self.count; if(key.equal(py::str("value"))) return self.value; if(key.equal(py::str("_sum_of_deltas_squared"))) return self._sum_of_deltas_squared; throw py::key_error( py::str("{0} not one of count, value, _sum_of_deltas_squared") .format(key)); }) .def("__setitem__", [](mean& self, const py::str& key, double value) { if(key.equal(py::str("count"))) self.count = value; else if(key.equal(py::str("value"))) self.value = value; else if(key.equal(py::str("_sum_of_deltas_squared"))) self._sum_of_deltas_squared = value; else throw py::key_error( py::str("{0} not one of count, value, _sum_of_deltas_squared") .format(key)); }) .def("_ipython_key_completions_", [](const py::object& /* self */) { return py::make_tuple("count", "value", "_sum_of_deltas_squared"); }) ; } scikit-hep-boost-histogram-5f6f3e3/src/register_algorithm.cpp000066400000000000000000000220301516627434300245110ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include // NOLINTNEXTLINE(misc-use-internal-linkage) void register_algorithms(py::module& algorithm) { py::class_(algorithm, "reduce_command") .def(py::init()) .def_readwrite("iaxis", &bh::algorithm::reduce_command::iaxis) .def("__repr__", [](const bh::algorithm::reduce_command& self) { using range_t = bh::algorithm::reduce_command::range_t; if(self.range != range_t::none) { const char* suffix = self.merge > 0 ? "_and_rebin" : ""; const char* c_start = self.iaxis == bh::algorithm::reduce_command::unset ? "" : "iaxis={0}, "; const char* c_merge = self.merge > 0 ? ", merge={0}" : ""; py::str const start = py::str(c_start).format(self.iaxis); py::str const merge = py::str(c_merge).format(self.merge); if(self.range == range_t::indices) { return py::str("reduce_command(slice{0}({1}begin={2}, " "end={3}{4}, mode={5}))") .format(suffix, start, self.begin.index, self.end.index, merge, self.crop ? "slice_mode.crop" : "slice_mode.shrink"); } return py::str( "reduce_command(shrink{0}({1}, lower={2}, upper={3}{4}))") .format(suffix, start, self.begin.value, self.end.value, merge); } // self.range == range_t::none return py::str("reduce_command(merge({0}))").format(self.merge); }); using slice_mode = bh::algorithm::slice_mode; py::enum_(algorithm, "slice_mode") .value("shrink", slice_mode::shrink) .value("crop", slice_mode::crop); algorithm .def("shrink_and_rebin", py::overload_cast( &bh::algorithm::shrink_and_rebin), "iaxis"_a, "lower"_a, "upper"_a, "merge"_a, "Shrink and rebin option to be used in reduce().\n" "\n" "To shrink and rebin in one command. Equivalent to passing both the " "shrink() and the\n" "rebin() option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param lower: lowest bound that should be kept.\n" ":param upper: highest bound that should be kept. If upper is inside " "bin interval, the whole " "interval is removed.\n" ":param merge: how many adjacent bins to merge into one.") .def("shrink_and_rebin", py::overload_cast( &bh::algorithm::shrink_and_rebin), "lower"_a, "upper"_a, "merge"_a, "Positional shrink and rebin option to be used in reduce().\n" "\n" "To shrink and rebin in one command. Equivalent to passing both the " "shrink() and the\n" "rebin() option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param lower: lowest bound that should be kept.\n" ":param upper: highest bound that should be kept. If upper is inside " "bin interval, the whole " "interval is removed.\n" ":param merge: how many adjacent bins to merge into one.") .def("crop_and_rebin", py::overload_cast( &bh::algorithm::crop_and_rebin), "iaxis"_a, "lower"_a, "upper"_a, "merge"_a, "Crop and rebin option to be used in reduce().\n" "\n" "To crop and rebin in one command. Equivalent to passing both the " "crop and the\n" "rebin option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param lower: lowest bound that should be kept.\n" ":param upper: highest bound that should be kept. If upper is inside " "bin interval, the whole " "interval is removed.\n" ":param merge: how many adjacent bins to merge into one.") .def( "crop_and_rebin", py::overload_cast(&bh::algorithm::crop_and_rebin), "lower"_a, "upper"_a, "merge"_a, "Positional crop and rebin option to be used in reduce().\n" "\n" "To crop and rebin in one command. Equivalent to passing both the " "crop and the\n" "rebin option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param lower: lowest bound that should be kept.\n" ":param upper: highest bound that should be kept. If upper is inside " "bin interval, the whole " "interval is removed.\n" ":param merge: how many adjacent bins to merge into one.") .def("slice_and_rebin", py::overload_cast(&bh::algorithm::slice_and_rebin), "iaxis"_a, "begin"_a, "end"_a, "merge"_a, "mode"_a = slice_mode::shrink, "Slice and rebin option to be used in reduce().\n" "\n" "To slice and rebin in one command. Equivalent to passing both the " "slice() and the\n" "rebin() option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param begin: first index that should be kept.\n" ":param end: one past the last index that should be kept.\n" ":param merge: how many adjacent bins to merge into one.\n" ":param mode: see slice_mode") .def("slice_and_rebin", py::overload_cast(&bh::algorithm::slice_and_rebin), "begin"_a, "end"_a, "merge"_a, "mode"_a = slice_mode::shrink, "Positional slice and rebin option to be used in reduce().\n" "\n" "To slice and rebin in one command. Equivalent to passing both the " "slice() and the\n" "rebin() option for the same axis to reduce.\n" "\n" ":param iaxis: which axis to operate on.\n" ":param begin: first index that should be kept.\n" ":param end: one past the last index that should be kept.\n" ":param merge: how many adjacent bins to merge into one.\n" ":param mode: see slice_mode") .def("rebin", py::overload_cast(&bh::algorithm::rebin), "iaxis"_a, "merge"_a) .def("rebin", py::overload_cast(&bh::algorithm::rebin), "merge"_a) .def("shrink", py::overload_cast(&bh::algorithm::shrink), "iaxis"_a, "lower"_a, "upper"_a) .def("shrink", py::overload_cast(&bh::algorithm::shrink), "lower"_a, "upper"_a) .def("crop", py::overload_cast(&bh::algorithm::crop), "iaxis"_a, "lower"_a, "upper"_a) .def("crop", py::overload_cast(&bh::algorithm::crop), "lower"_a, "upper"_a) .def("slice", py::overload_cast(&bh::algorithm::slice), "iaxis"_a, "begin"_a, "end"_a, "mode"_a = slice_mode::shrink) .def("slice", py::overload_cast( &bh::algorithm::slice), "begin"_a, "end"_a, "mode"_a = slice_mode::shrink) ; } scikit-hep-boost-histogram-5f6f3e3/src/register_axis.cpp000066400000000000000000000066401516627434300235000ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include #include #include #include #include #include namespace { template void register_axis_each(py::module& mod, Func&& function) { using namespace boost::mp11; using types = mp_list; mp_for_each>([&](auto I) { using T = mp_at_c; auto ax = register_axis(mod); function(ax); }); } } // namespace // NOLINTNEXTLINE(misc-use-internal-linkage) void register_axes(py::module& mod) { register_axis_each(mod, [](auto ax) { ax.def(py::init(), "bins"_a, "start"_a, "stop"_a); }); register_axis(mod) .def(py::init([](unsigned n, double start, double stop, double pow) { return new axis::regular_pow( bh::axis::transform::pow{pow}, n, start, stop); }), "bins"_a, "start"_a, "stop"_a, "power"_a) .def_property_readonly("transform", [](const axis::regular_pow& self) { return self.transform(); }); register_axis(mod) .def(py::init([](unsigned n, double start, double stop, func_transform& trans) { return new axis::regular_trans(trans, n, start, stop); }), "bins"_a, "start"_a, "stop"_a, "transform"_a) .def_property_readonly("transform", [](const axis::regular_trans& self) { return self.transform(); }); register_axis_each( mod, [](auto ax) { ax.def(py::init>(), "edges"_a); }); register_axis_each( mod, [](auto ax) { ax.def(py::init(), "start"_a, "stop"_a); }); register_axis_each( mod, [](auto ax) { ax.def(py::init>(), "categories"_a); }); register_axis_each(mod, [](auto ax) { ax.def(py::init>(), "categories"_a); }); register_axis(mod, "boolean").def(py::init<>()); ; } scikit-hep-boost-histogram-5f6f3e3/src/register_histograms.cpp000066400000000000000000000036171516627434300247150ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include #include #include #include #include // NOLINTNEXTLINE(misc-use-internal-linkage) void register_histograms(py::module& hist) { hist.attr("_axes_limit") = BOOST_HISTOGRAM_DETAIL_AXES_LIMIT; register_histogram( hist, "any_int64", "N-dimensional histogram for unlimited size data with any axis types."); register_histogram( hist, "any_unlimited", "N-dimensional histogram for unlimited size data with any axis types."); register_histogram( hist, "any_double", "N-dimensional histogram for real-valued data with weights with any axis " "types."); register_histogram( hist, "any_atomic_int64", "N-dimensional histogram for threadsafe integer data with any axis types."); register_histogram( hist, "any_weight", "N-dimensional histogram for weighted data with any axis types."); register_histogram( hist, "any_mean", "N-dimensional histogram for sampled data with any axis types."); register_histogram( hist, "any_weighted_mean", "N-dimensional histogram for weighted and sampled data with any axis types."); register_histogram( hist, "any_multi_cell", "N-dimensional histogram for storing multiple cells at once with any axis " "types."); } scikit-hep-boost-histogram-5f6f3e3/src/register_storage.cpp000066400000000000000000000030231516627434300241700ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include #include #include // NOLINTNEXTLINE(misc-use-internal-linkage) void register_storages(py::module& storage) { register_storage( storage, "int64", "Integers in vectors storage type"); register_storage( storage, "double", "Weighted storage without variance type (fast but simple)"); register_storage( storage, "atomic_int64", "Threadsafe (not growing axis) integer storage"); register_storage( storage, "unlimited", "Optimized for unweighted histograms, adaptive"); register_storage( storage, "weight", "Dense storage which tracks sums of weights and a variance estimate"); register_storage( storage, "mean", "Dense storage which tracks means of samples in each cell"); register_storage( storage, "weighted_mean", "Dense storage which tracks means of weighted samples in each cell"); register_storage( storage, "multi_cell", "Dense storage which tracks sums of cell entries for multiple cells per entry"); } scikit-hep-boost-histogram-5f6f3e3/src/register_transforms.cpp000066400000000000000000000066051516627434300247330ustar00rootroot00000000000000// Copyright 2018-2019 Henry Schreiner and Hans Dembinski // // Distributed under the 3-Clause BSD License. See accompanying // file LICENSE or https://github.com/scikit-hep/boost-histogram for details. #include #include #include #include #include #include #include namespace { template py::class_ register_transform(py::module& mod, Args&&... args) { py::class_ transform(mod, std::forward(args)...); transform .def(py::init()) .def("forward", [](const T& self, double v) { return self.forward(v); }) .def("inverse", [](const T& self, double v) { return self.inverse(v); }) .def(make_pickle()) .def("__copy__", [](const T& self) { return T(self); }) .def("__deepcopy__", &deep_copy) ; return transform; } } // namespace extern "C" { double _log_fn(double v) { return std::log(v); } double _exp_fn(double v) { return std::exp(v); } double _sqrt_fn(double v) { return std::sqrt(v); } double _sq_fn(double v) { return v * v; } } // NOLINTNEXTLINE(misc-use-internal-linkage) void register_transforms(py::module& mod) { mod.def("_log_fn", &_log_fn); mod.def("_exp_fn", &_exp_fn); mod.def("_sqrt_fn", &_sqrt_fn); mod.def("_sq_fn", &_sq_fn); register_transform(mod, "id") .def(py::init<>()) .def("__repr__", [](const py::object& self) { return py::str("{}()").format(self.attr("__class__").attr("__name__")); }) ; register_transform(mod, "sqrt") .def(py::init<>()) .def("__repr__", [](const py::object& self) { return py::str("{}()").format(self.attr("__class__").attr("__name__")); }) ; register_transform(mod, "log") .def(py::init<>()) .def("__repr__", [](const py::object& self) { return py::str("{}()").format(self.attr("__class__").attr("__name__")); }) ; register_transform(mod, "pow") .def(py::init(), "power"_a) .def_readonly("power", &bh::axis::transform::pow::power) .def("__repr__", [](const py::object& self) { double const power = py::cast(self).power; return py::str("{}({:g})") .format(self.attr("__class__").attr("__name__"), power); }) ; register_transform(mod, "func_transform") .def(py::init(), "forward"_a, "inverse"_a, "convert"_a, "name"_a) .def("__repr__", [](const py::object& self) { auto& s = py::cast(self); if(s._name.equal(py::str(""))) { return py::str("{}({}, {})") .format(self.attr("__class__").attr("__name__"), s._forward_ob, s._inverse_ob); } return s._name; }) ; } scikit-hep-boost-histogram-5f6f3e3/tests/000077500000000000000000000000001516627434300204715ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/tests/CMakeLists.txt000066400000000000000000000023401516627434300232300ustar00rootroot00000000000000pybind11_find_import(numpy) pybind11_find_import(pytest) pybind11_find_import(pytest-benchmark) # Much better test target on CMake 3.29+ set(CMAKE_SKIP_TEST_ALL_DEPENDENCY FALSE) # Support for running from build directory file(WRITE "${PROJECT_BINARY_DIR}/pytest.ini" "[pytest]\n" "addopts = --benchmark-disable\n" "testpaths = ${CMAKE_CURRENT_SOURCE_DIR}\n") # Support plain "pytest" in addition to "python -m pytest" file(WRITE "${PROJECT_BINARY_DIR}/conftest.py" "import sys\n" "sys.path.insert(0, '${PROJECT_BINARY_DIR}')\n") # Look for all the tests file(GLOB BOOST_HIST_PY_TESTS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_*.py") # Add each test foreach(TEST_FILE IN LISTS BOOST_HIST_PY_TESTS) get_filename_component(TEST_NAME "${TEST_FILE}" NAME_WE) add_test( NAME ${TEST_NAME} COMMAND ${Python_EXECUTABLE} -m pytest "${TEST_FILE}" --rootdir=. --showlocals WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") set_tests_properties(${TEST_NAME} PROPERTIES SKIP_RETURN_CODE 5) endforeach() add_test( NAME boost_histogram_test COMMAND ${Python_EXECUTABLE} -m boost_histogram.test WORKING_DIRECTORY "${PROJECT_BINARY_DIR}") scikit-hep-boost-histogram-5f6f3e3/tests/conftest.py000066400000000000000000000047251516627434300227000ustar00rootroot00000000000000from __future__ import annotations import contextlib import importlib.metadata import sys import sysconfig from pathlib import Path if sys.version_info < (3, 11): import tomli as tomllib else: import tomllib import pytest from packaging.requirements import Requirement import boost_histogram as bh DIR = Path(__file__).parent BASE = DIR.parent @pytest.fixture(params=(False, True), ids=("no_growth", "growth")) def growth(request): return request.param @pytest.fixture(params=(False, True), ids=("no_overflow", "overflow")) def overflow(request): return request.param @pytest.fixture(params=(False, True), ids=("no_underflow", "underflow")) def underflow(request): return request.param @pytest.fixture(params=(False, True), ids=("no_flow", "flow")) def flow(request): return request.param @pytest.fixture( params=(None, "str", 1, {"a": 1}), ids=("no_metadata", "str_metadata", "int_metadata", "dict_metadata"), ) def metadata(request): return request.param @pytest.fixture( params=( bh.storage.Double, bh.storage.Int64, bh.storage.AtomicInt64, bh.storage.Weight, bh.storage.Unlimited, ), ids=("Double", "Int64", "AtomicInt64", "Weight", "Unlimited"), ) def count_storage(request): return request.param @pytest.fixture( params=( bh.storage.Double, bh.storage.Int64, bh.storage.AtomicInt64, bh.storage.Unlimited, ), ids=("Double", "Int64", "AtomicInt64", "Unlimited"), ) def count_single_storage(request): return request.param def pytest_report_header() -> str: with BASE.joinpath("pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) project = pyproject.get("project", {}) pkgs = project.get("dependencies", []) pkgs += [p for ps in project.get("optional-dependencies", {}).values() for p in ps] if "name" in project: pkgs.append(project["name"]) interesting_packages = {Requirement(p).name for p in pkgs} interesting_packages.add("pip") interesting_packages.add("numpy") valid = [] for package in sorted(interesting_packages): with contextlib.suppress(ModuleNotFoundError): valid.append(f"{package}=={importlib.metadata.version(package)}") reqs = " ".join(valid) lines = [ f"installed packages of interest: {reqs}", ] if sysconfig.get_config_var("Py_GIL_DISABLED"): lines.append("free-threaded Python build") return "\n".join(lines) scikit-hep-boost-histogram-5f6f3e3/tests/pickles/000077500000000000000000000000001516627434300221235ustar00rootroot00000000000000scikit-hep-boost-histogram-5f6f3e3/tests/pickles/bh_0.10.2.pkl000066400000000000000000000023561516627434300240300ustar00rootroot00000000000000€}q(Xh1qcboost_histogram Histogram q)�q}q(X_histqcboost_histogram._core.hist any_double q)�q(KKKKKKKKNGÀ.G@>KKcnumpy.core.multiarray _reconstruct qcnumpy ndarray q K…q c_codecs encode q Xbq Xlatin1q †qRq‡qRq(KK!…qcnumpy dtype qXf8q‰ˆ‡qRq(KXbNKKhh K…q?h‡q@RqA(KK …qBh‰h XSð?ð?ð?qCh †qDRqEtqFbtqGbhNubXversionqHX0.10.2qIu.scikit-hep-boost-histogram-5f6f3e3/tests/pickles/bh_0.11.1.pkl000066400000000000000000000024251516627434300240250ustar00rootroot00000000000000€}q(Xh1qcboost_histogram Histogram q)�qK}q(XmetadataqNX_histqcboost_histogram._core.hist any_double q)�q(KKKKKKKK}q hNsGÀ.G@>KKcnumpy.core.multiarray _reconstruct q cnumpy ndarray q K…q c_codecs encode q XbqXlatin1q†qRq‡qRq(KK!…qcnumpy dtype qXf8q‰ˆ‡qRq(KX(KK…q?h‰h X!ð?@@q@h†qARqBtqCb}qDhNsKKh h K…qEh‡qFRqG(KK …qHh‰h XSð?ð?ð?qIh†qJRqKtqLbtqMbu†qNbXversionqOX0.11.1qPu.scikit-hep-boost-histogram-5f6f3e3/tests/pickles/bh_0.6.2.pkl000066400000000000000000000023131516627434300237460ustar00rootroot00000000000000€}q(Xh1qcboost_histogram Histogram q)�q}qX_histqcboost_histogram._core.hist any_double q)�q(KKKKKKKKNGÀ.G@>KKcnumpy.core.multiarray _reconstruct qcnumpy ndarray q K…q c_codecs encode q Xbq Xlatin1q †qRq‡qRq(KK!…qcnumpy dtype qXf8q‰ˆ‡qRq(KXRq?(KK …q@h‰h XSð?ð?ð?qAh †qBRqCtqDbtqEbsbXversionqFX0.6.2qGu.scikit-hep-boost-histogram-5f6f3e3/tests/pickles/bh_1.1.0.pkl000066400000000000000000000025621516627434300237460ustar00rootroot00000000000000€}q(Xh1qcboost_histogram Histogram q)�qK}q(X_variance_knownqˆXmetadataqNX_histqcboost_histogram._core.hist any_int64 q)�q (KKKKKKKK}q GÀ.G@>KKcnumpy.core.multiarray _reconstruct q cnumpy ndarray q K…q c_codecs encode qXbqXlatin1q†qRq‡qRq(KK!…qcnumpy dtype qXu8q‰ˆ‡qRq(KX(KKKKKKKXoneq?Xtwoq@}qAKKK Kh h K…qBh‡qCRqD(KK…qEh2‰hX!ð?@@qFh†qGRqHtqIb}qJKKh h K…qKh‡qLRqM(KK …qNh2‰hXSð?ð?ð?qOh†qPRqQtqRbtqSbu†qTbXversionqUX1.1.0qVu.scikit-hep-boost-histogram-5f6f3e3/tests/pickles/make_pickle.py000077500000000000000000000022601516627434300247440ustar00rootroot00000000000000#!/usr/bin/env python3 from __future__ import annotations import pickle from pathlib import Path import typer import boost_histogram as bh DIR = Path(__file__).parent def make_pickle( output: Path | None = typer.Argument(None, exists=False), # noqa: B008 *, protocol: int = 2, ) -> None: """ Make a pickle file with the current boost-histogram for use in tests. """ VER = tuple(map(int, bh.__version__.split(".")[:3])) if output is None: output = DIR / f"bh_{bh.__version__}.pkl" h1 = bh.Histogram(bh.axis.Regular(31, -15, 15), storage=bh.storage.Int64()) h2 = bh.Histogram( bh.axis.Integer(0, 5, metadata={"hello": "world"}), storage=bh.storage.Weight() ) if VER >= (0, 9, 0): h2.metadata = "foo" h3 = bh.Histogram( bh.axis.StrCategory([], growth=True), bh.axis.Variable([0, 1, 2, 3]) ) h1.fill([-5, 1, 1, 2]) h2.fill([1, 2, 2, 3, 3, 3]) h3.fill(["one", "two", "two"], [3, 2, 1]) with output.open("wb") as f: pickle.dump( {"h1": h1, "h2": h2, "h3": h3, "version": bh.__version__}, f, protocol ) if __name__ == "__main__": typer.run(make_pickle) scikit-hep-boost-histogram-5f6f3e3/tests/test_accumulators.py000066400000000000000000000077121516627434300246130ustar00rootroot00000000000000from __future__ import annotations import pytest from pytest import approx import boost_histogram as bh hypothesis = pytest.importorskip("hypothesis") st = pytest.importorskip("hypothesis.strategies") def test_weighted_sum(): a = bh.accumulators.WeightedSum(1.5, 2.5) assert repr(a) == "WeightedSum(value=1.5, variance=2.5)" assert a == bh.accumulators.WeightedSum(1.5, 2.5) assert a.value == 1.5 assert a.variance == 2.5 a += 1.5 assert a.value == 3.0 assert a.variance == 4.75 vals = [1, 2, 3] vari = [4, 5, 6] a = bh.accumulators.WeightedSum() for val, var in zip(vals, vari, strict=False): a += bh.accumulators.WeightedSum(val, variance=var) assert a.value == 6 assert a.variance == 15 a2 = bh.accumulators.WeightedSum().fill(vals, variance=vari) assert a == a2 assert a == bh.accumulators.WeightedSum(6, 15) def test_sum(): vals = [1, 2, 3] a = bh.accumulators.Sum() for val in vals: a += val assert a.value == 6 a2 = bh.accumulators.Sum().fill(vals) assert a == a2 assert a == bh.accumulators.Sum(6) assert repr(a) == "Sum(6 + 0)" def test_weighted_mean(): vals = [4, 1] weights = [1, 2] a = bh.accumulators.WeightedMean() for v, w in zip(vals, weights, strict=False): a(v, weight=w) assert a.sum_of_weights == 3.0 assert a.variance == 4.5 assert a.value == 2.0 a2 = bh.accumulators.WeightedMean().fill(vals, weight=weights) assert a == a2 assert a == bh.accumulators.WeightedMean(3, 5, 2, 4.5) assert ( repr(a) == "WeightedMean(sum_of_weights=3, sum_of_weights_squared=5, value=2, variance=4.5)" ) def test_mean(): vals = [1, 2, 3] a = bh.accumulators.Mean() for val in vals: a(val) assert a.count == 3 assert a.value == 2 assert a.variance == 1 a2 = bh.accumulators.Mean().fill([1, 2, 3]) assert a == a2 assert a == bh.accumulators.Mean(3, 2, 1) assert repr(a) == "Mean(count=3, value=2, variance=1)" float_st = st.floats( allow_nan=False, allow_infinity=False, min_value=-1e4, max_value=1e4 ) simple_list_st = st.lists(float_st, min_size=1, max_size=10) @hypothesis.given( simple_list_st, simple_list_st, ) def test_sum_mean(list1, list2): a = bh.accumulators.Mean() a.fill(list1) b = bh.accumulators.Mean() b.fill(list2) c = bh.accumulators.Mean() c.fill(list1 + list2) ab = a + b assert ab.value == approx(c.value) assert ab.variance == approx(c.variance, nan_ok=True, abs=1e-7, rel=1e-3) assert ab.count == approx(c.count) a += b assert a.value == approx(c.value) assert a.variance == approx(c.variance, nan_ok=True, abs=1e-7, rel=1e-3) assert a.count == approx(c.count) dual_lists_st = st.integers(min_value=1, max_value=10).flatmap( lambda n: st.tuples( st.lists(float_st, min_size=n, max_size=n), st.lists( st.floats( allow_nan=False, allow_infinity=False, min_value=1e-2, max_value=1e2 ), min_size=n, max_size=n, ), ) ) @hypothesis.given(dual_lists_st, dual_lists_st) def test_sum_weighed_mean(pair1, pair2): a = bh.accumulators.WeightedMean() a.fill(pair1[0], weight=pair1[1]) b = bh.accumulators.WeightedMean() b.fill(pair2[0], weight=pair2[1]) c = bh.accumulators.WeightedMean() c.fill(pair1[0] + pair2[0], weight=pair1[1] + pair2[1]) ab = a + b assert ab.value == approx(c.value) assert ab.variance == approx(c.variance, nan_ok=True, abs=1e-3, rel=1e-2) assert ab.sum_of_weights == approx(c.sum_of_weights) assert ab.sum_of_weights_squared == approx(c.sum_of_weights_squared) a += b assert a.value == approx(c.value) assert a.variance == approx(c.variance, nan_ok=True, abs=1e-3, rel=1e-2) assert a.sum_of_weights == approx(c.sum_of_weights) assert a.sum_of_weights_squared == approx(c.sum_of_weights_squared) scikit-hep-boost-histogram-5f6f3e3/tests/test_axes_object.py000066400000000000000000000046261516627434300244000ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest from pytest import approx import boost_histogram as bh @pytest.fixture def h(): return bh.Histogram( bh.axis.Regular(10, 0, 10, metadata=2), bh.axis.Integer(0, 5, metadata="hi"), bh.axis.StrCategory(["HI", "HO"]), ) def test_axes_basics(h): h = bh.Histogram( bh.axis.Regular(10, 0, 10, metadata=2), bh.axis.Integer(0, 5, metadata="hi"), bh.axis.StrCategory(["HI", "HO"]), ) assert h.axes.bin(1, 2, 0) == ((1.0, 2.0), 2, "HI") assert h.axes.value(0, 0, 0) == (0.0, 0, "HI") assert h.axes.index(2, 3, "HO") == (2, 3, 1) assert h.axes.size == (10, 5, 2) assert h.axes.extent == (12, 7, 3) assert h.axes.metadata == (2, "hi", None) h.axes.metadata = None, 3, "bye" assert h.axes.metadata == (None, 3, "bye") def test_axes_centers(h): centers = h.axes.centers answers = np.ogrid[0.5:10, 0.5:5, 0.5:2] full_answers = np.mgrid[0.5:10, 0.5:5, 0.5:2] for i in range(3): assert centers.broadcast()[i] == approx(full_answers[i]) assert centers[i] == approx(answers[i]) assert centers.T[i] == approx(answers[i].T) assert centers.flatten()[i] == approx(answers[i].flatten()) assert h.axes[i].centers == approx(answers[i].ravel()) def test_axes_edges(h): edges = h.axes.edges answers = np.ogrid[0:11, 0:6, 0:3] full_answers = np.mgrid[0:11, 0:6, 0:3] for i in range(3): assert edges.broadcast()[i] == approx(full_answers[i]) assert edges[i] == approx(answers[i]) assert edges.T[i] == approx(answers[i].T) assert edges.ravel()[i] == approx(answers[i].ravel()) assert h.axes[i].edges == approx(answers[i].ravel()) def test_axes_widths(h): widths = h.axes.widths answers = np.ogrid[1:1:10j, 1:1:5j, 1:1:2j] full_answers = np.mgrid[1:1:10j, 1:1:5j, 1:1:2j] for i in range(3): assert widths.broadcast()[i] == approx(full_answers[i]) assert widths[i] == approx(answers[i]) assert widths.T[i] == approx(answers[i].T) assert widths.ravel()[i] == approx(answers[i].ravel()) assert h.axes[i].widths == approx(answers[i].ravel()) def test_axis_misconstuct(): inp = [bh.axis.Regular(12, 0, 1)] ok = bh.axis.AxesTuple(inp) assert ok[0] == inp[0] with pytest.raises(TypeError): bh.axis.AxesTuple(inp[0]) scikit-hep-boost-histogram-5f6f3e3/tests/test_axis.py000066400000000000000000000752171516627434300230620ustar00rootroot00000000000000from __future__ import annotations import abc import copy import numpy as np import pytest from numpy.testing import assert_allclose, assert_array_equal from pytest import approx import boost_histogram as bh @pytest.mark.parametrize( ("axis", "args", "opt", "kwargs"), [ (bh.axis.Regular, (1, 2, 3), "", {}), (bh.axis.Regular, (1, 2, 3), "u", {}), (bh.axis.Regular, (1, 2, 3), "o", {}), (bh.axis.Regular, (1, 2, 3), "uo", {}), (bh.axis.Regular, (1, 2, 3), "g", {}), (bh.axis.Regular, (1, 2, 3), "", {"circular": True}), (bh.axis.Regular, (1, 2, 3), "", {"transform": bh.axis.transform.log}), (bh.axis.Regular, (1, 2, 3), "", {"transform": bh.axis.transform.sqrt}), (bh.axis.Regular, (1, 2, 3), "", {"transform": bh.axis.transform.Pow(1)}), (bh.axis.Variable, ((1, 2, 3),), "", {}), (bh.axis.Variable, ((1, 2, 3),), "u", {}), (bh.axis.Variable, ((1, 2, 3),), "o", {}), (bh.axis.Variable, ((1, 2, 3),), "uo", {}), (bh.axis.Variable, ((1, 2, 3),), "g", {}), (bh.axis.Variable, ((1, 2, 3),), "", {"circular": True}), (bh.axis.Integer, (1, 2), "", {}), (bh.axis.Integer, (1, 2), "u", {}), (bh.axis.Integer, (1, 2), "o", {}), (bh.axis.Integer, (1, 2), "uo", {}), (bh.axis.Integer, (1, 2), "g", {}), (bh.axis.Integer, (1, 2), "", {"circular": True}), (bh.axis.IntCategory, ((1, 2, 3),), "", {}), (bh.axis.IntCategory, ((1, 2, 3),), "o", {}), (bh.axis.IntCategory, ((1, 2, 3),), "g", {}), (bh.axis.IntCategory, ((1, 2, 3),), "go", {}), (bh.axis.IntCategory, ((),), "g", {}), (bh.axis.StrCategory, (tuple("ABC"),), "", {}), (bh.axis.StrCategory, (tuple("ABC"),), "o", {}), (bh.axis.StrCategory, (tuple("ABC"),), "g", {}), (bh.axis.StrCategory, (tuple("ABC"),), "go", {}), (bh.axis.StrCategory, ((),), "g", {}), ], ) def test_metadata(axis, args, opt, kwargs): for m in ("foo", 64, {"one": 1}): if "u" in opt: kwargs["underflow"] = False if "o" in opt: kwargs["overflow"] = False if "g" in opt: kwargs["growth"] = True kwargs["metadata"] = m assert axis(*args, **kwargs).metadata is m mcopy = copy.deepcopy(m) assert axis(*args, **kwargs).metadata == m assert axis(*args, **kwargs).metadata == mcopy assert axis(*args, **kwargs).metadata != "bar" assert axis(*args, **kwargs) == axis(*args, **kwargs) assert axis(*args, **kwargs) != axis(*args, metadata="bar") del kwargs["metadata"] ax = axis(*args, __dict__={"metadata": 3, "other": 2}) assert ax.metadata == 3 assert ax.other == 2 del ax.__dict__ assert ax.__dict__ == {} assert ax.metadata is None ax.__dict__ = {"metadata": 5} assert ax.__dict__ == {"metadata": 5} assert ax.metadata == 5 # Python 2 did not allow mixing ** and kw new_kwargs = copy.copy(kwargs) new_kwargs["__dict__"] = {"something": 2} new_kwargs["metadata"] = 3 with pytest.raises(KeyError): axis(*args, **new_kwargs) new_kwargs = copy.copy(kwargs) new_kwargs["__dict__"] = {"metadata": 2} new_kwargs["metadata"] = 3 with pytest.raises(KeyError): axis(*args, **new_kwargs) # The point of this ABC is to force all the tests listed here to be implemented # for each axis type. Pytest instantiates these test classes for us, so missing # one really does fail the test. class Axis(abc.ABC): @abc.abstractmethod def test_init(self): pass @abc.abstractmethod def test_traits(self): pass @abc.abstractmethod def test_equal(self): pass @abc.abstractmethod def test_len(self): pass @abc.abstractmethod def test_repr(self): pass @abc.abstractmethod def test_getitem(self): pass @abc.abstractmethod def test_iter(self): pass @abc.abstractmethod def test_index(self): pass @abc.abstractmethod def test_edges_centers_widths(self): pass class TestRegular(Axis): def test_init(self): # Should not throw bh.axis.Regular(1, 1.0, 2.0) bh.axis.Regular(1, 1.0, 2.0, metadata="ra") bh.axis.Regular(1, 1.0, 2.0, underflow=False) bh.axis.Regular(1, 1.0, 2.0, underflow=False, overflow=False, metadata="ra") bh.axis.Regular(1, 1.0, 2.0, metadata=0) bh.axis.Regular(1, 1.0, 2.0, transform=bh.axis.transform.log) bh.axis.Regular(1, 1.0, 2.0, transform=bh.axis.transform.sqrt) bh.axis.Regular(1, 1.0, 2.0, transform=bh.axis.transform.Pow(1.5)) with pytest.raises(TypeError): bh.axis.Regular() with pytest.raises(TypeError): bh.axis.Regular(overflow=False, underflow=False) with pytest.raises(TypeError): bh.axis.Regular(1) with pytest.raises(TypeError): bh.axis.Regular(1, 1.0) with pytest.raises(ValueError): bh.axis.Regular(0, 1.0, 2.0) with pytest.raises(TypeError): bh.axis.Regular("1", 1.0, 2.0) with pytest.raises(TypeError): bh.axis.Regular(-1, 1.0, 2.0) with pytest.raises(ValueError): bh.axis.Regular(1, 1.0, 1.0) with pytest.raises(TypeError): bh.axis.Regular(1, 1.0, 2.0, bad_keyword="ra") with pytest.raises(AttributeError): bh.axis.Regular(1, 1.0, 2.0, transform=lambda _: 2) with pytest.raises(TypeError): bh.axis.Regular(1, 1.0, 2.0, transform=bh.axis.transform.Pow) # TODO: These errors could be better def test_traits(self): STD_TRAITS = {"continuous": True, "ordered": True} ax = bh.axis.Regular(1, 2, 3) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits(underflow=True, overflow=True, **STD_TRAITS) ax = bh.axis.Regular(1, 2, 3, overflow=False) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits(underflow=True, **STD_TRAITS) ax = bh.axis.Regular(1, 2, 3, underflow=False) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits(overflow=True, **STD_TRAITS) ax = bh.axis.Regular(1, 2, 3, underflow=False, overflow=False) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits(**STD_TRAITS) ax = bh.axis.Regular(1, 2, 3, growth=True) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits( underflow=True, overflow=True, growth=True, **STD_TRAITS ) def test_equal(self): a = bh.axis.Regular(4, 1.0, 2.0) assert a == bh.axis.Regular(4, 1.0, 2.0) assert a != bh.axis.Regular(3, 1.0, 2.0) assert a != bh.axis.Regular(4, 1.1, 2.0) assert a != bh.axis.Regular(4, 1.0, 2.1) assert a != object() assert a != object() # __eq__ and __ne__ are separately implemented # metadata compare assert bh.axis.Regular(1, 2, 3, metadata=1) == bh.axis.Regular( 1, 2, 3, metadata=1 ) assert bh.axis.Regular(1, 2, 3, metadata=1) != bh.axis.Regular( 1, 2, 3, metadata="1" ) assert bh.axis.Regular(1, 2, 3, metadata=1) != bh.axis.Regular( 1, 2, 3, metadata=[1] ) def test_len(self): a = bh.axis.Regular(4, 1.0, 2.0) assert len(a) == 4 assert a.size == 4 assert a.extent == 6 def test_repr(self): ax = bh.axis.Regular(4, 1.1, 2.2) assert repr(ax) == "Regular(4, 1.1, 2.2)" ax = bh.axis.Regular(4, 1.1, 2.2, metadata="ra") assert repr(ax) == "Regular(4, 1.1, 2.2, metadata='ra')" ax = bh.axis.Regular(4, 1.1, 2.2, underflow=False) assert repr(ax) == "Regular(4, 1.1, 2.2, underflow=False)" ax = bh.axis.Regular(4, 1.1, 2.2, metadata="ra", overflow=False) assert repr(ax) == "Regular(4, 1.1, 2.2, overflow=False, metadata='ra')" ax = bh.axis.Regular(4, 1.1, 2.2, metadata="ra", circular=True) assert repr(ax) == "Regular(4, 1.1, 2.2, circular=True, metadata='ra')" ax = bh.axis.Regular(4, 1.1, 2.2, transform=bh.axis.transform.log) assert repr(ax) == "Regular(4, 1.1, 2.2, transform=log)" # TODO: Add caching so that an extracted functional transform actually works ax = bh.axis.Regular(3, 1.1, 2.2, transform=bh.axis.transform.sqrt) assert repr(ax) == "Regular(3, 1.1, 2.2, transform=sqrt)" ax = bh.axis.Regular(4, 1.1, 2.2, transform=bh.axis.transform.Pow(0.5)) assert repr(ax) == "Regular(4, 1.1, 2.2, transform=pow(0.5))" def test_getitem(self): a = bh.axis.Regular(2, 1.0, 2.0) ref = [1.0, 1.5, 2.0] for i in range(2): assert_allclose(a.bin(i), ref[i : i + 2]) assert_allclose(a[i], ref[i : i + 2]) assert a[-1] == a[1] with pytest.raises(IndexError): a[2] assert a.bin(-1)[0] == -np.inf assert a.bin(2)[1] == np.inf assert_allclose(a[bh.underflow], a.bin(-1)) assert_allclose(a[bh.overflow], a.bin(2)) with pytest.raises(IndexError): a.bin(-2) with pytest.raises(IndexError): a.bin(3) def test_iter(self): a = bh.axis.Regular(2, 1.0, 2.0) ref = [(1.0, 1.5), (1.5, 2.0)] assert_allclose(a, ref) def test_index(self): a = bh.axis.Regular(4, 1.0, 2.0) assert a.index(-1) == -1 assert a.index(0.99) == -1 assert a.index(1.0) == 0 assert a.index(1.249) == 0 assert a.index(1.250) == 1 assert a.index(1.499) == 1 assert a.index(1.500) == 2 assert a.index(1.749) == 2 assert a.index(1.750) == 3 assert a.index(1.999) == 3 assert a.index(2.000) == 4 assert a.index(20) == 4 def test_reversed_index(self): a = bh.axis.Regular(4, 2.0, 1.0) assert a.index(-1) == 4 assert a.index(0.99) == 4 assert a.index(1.0) == 4 assert a.index(1.249) == 3 assert a.index(1.250) == 3 assert a.index(1.499) == 2 assert a.index(1.500) == 2 assert a.index(1.749) == 1 assert a.index(1.750) == 1 assert a.index(1.999) == 0 assert a.index(2.000) == 0 assert a.index(20) == -1 def test_sqrt_transform(self): a = bh.axis.Regular(10, 0, 10, transform=bh.axis.transform.sqrt) # Edges: 0. , 0.1, 0.4, 0.9, 1.6, 2.5, 3.6, 4.9, 6.4, 8.1, 10. assert a.index(-100) == 10 # Always in overflow bin assert a.index(-1) == 10 # When transform is invalid assert a.index(0) == 0 assert a.index(0.15) == 1 assert a.index(0.5) == 2 assert a.index(1) == 3 assert a.index(1.7) == 4 assert a.index(9) == 9 assert a.index(11) == 10 assert a.index(1000) == 10 assert a.bin(0)[0] == approx(0) assert a.bin(1)[0] == approx(0.1) assert a.bin(1)[1] == approx(0.4) assert a.bin(2)[0] == approx(0.4) def test_log_transform(self): a = bh.axis.Regular(2, 1e0, 1e2, transform=bh.axis.transform.log) assert a.index(-1) == 2 assert a.index(0.99) == -1 assert a.index(1.001) == 0 assert a.index(9.99) == 0 assert a.index(10.01) == 1 assert a.index(99.9) == 1 assert a.index(100.01) == 2 assert a.index(1000.1) == 2 assert a.bin(0)[0] == approx(1e0) assert a.bin(1)[0] == approx(1e1) assert a.bin(1)[1] == approx(1e2) def test_pow_transform(self): a = bh.axis.Regular(2, 1.0, 9.0, transform=bh.axis.transform.Pow(0.5)) assert a.index(-1) == 2 assert a.index(0.99) == -1 assert a.index(1.0) == 0 assert a.index(3.99) == 0 assert a.index(4.0) == 1 assert a.index(8.99) == 1 assert a.index(9) == 2 assert a.index(1000) == 2 assert a.bin(0)[0] == approx(1.0) assert a.bin(1)[0] == approx(4.0) assert a.bin(1)[1] == approx(9.0) def test_edges_centers_widths(self): a = bh.axis.Regular(2, 0, 1) assert_allclose(a.edges, [0, 0.5, 1]) assert_allclose(a.centers, [0.25, 0.75]) assert_allclose(a.widths, [0.5, 0.5]) class TestCircular(Axis): def test_init(self): # Should not throw bh.axis.Regular(1, 2, 3, circular=True) bh.axis.Regular(1, 2, 3, metadata="pa", circular=True) with pytest.raises(TypeError): bh.axis.Regular(1, 2, 3, "pa", circular=True) with pytest.raises(TypeError): bh.axis.Regular(circular=True) with pytest.raises(TypeError): bh.axis.Regular(1, circular=True) with pytest.raises(TypeError): bh.axis.Regular(1, -1, circular=True) with pytest.raises(TypeError): bh.axis.Regular(-1, circular=True) with pytest.raises(TypeError): bh.axis.Regular(1, 1.0, metadata=1, circular=True) with pytest.raises(TypeError): bh.axis.Regular("1", circular=True) def test_traits(self): ax = bh.axis.Regular(1, 2, 3, circular=True) assert isinstance(ax, bh.axis.Regular) assert ax.traits == bh.axis.Traits( overflow=True, circular=True, continuous=True, ordered=True ) def test_equal(self): a = bh.axis.Regular(4, 0.0, 1.0, circular=True) assert a == bh.axis.Regular(4, 0, 1, circular=True) assert a != bh.axis.Regular(2, 0, 1, circular=True) assert isinstance(a, bh.axis.Regular) def test_len(self): assert len(bh.axis.Regular(4, 0.0, 1.0, circular=True)) == 4 assert bh.axis.Regular(4, 0.0, 1.0, circular=True).size == 4 assert bh.axis.Regular(4, 0.0, 1.0, circular=True).extent == 5 def test_repr(self): ax = bh.axis.Regular(4, 1.1, 2.2, circular=True) assert repr(ax) == "Regular(4, 1.1, 2.2, circular=True)" ax = bh.axis.Regular(4, 1.1, 2.2, metadata="hi", circular=True) assert repr(ax) == "Regular(4, 1.1, 2.2, circular=True, metadata='hi')" def test_getitem(self): a = bh.axis.Regular(2, 1, 1 + np.pi * 2, circular=True) ref = [1.0, 1.0 + np.pi, 1.0 + 2.0 * np.pi] for i in range(2): assert_allclose(a.bin(i), ref[i : i + 2]) assert_allclose(a[i], ref[i : i + 2]) assert a[-1] == a[1] with pytest.raises(IndexError): a[2] with pytest.raises(IndexError): a[bh.underflow] assert_allclose(a[bh.overflow], a.bin(2)) assert a.bin(2)[0] == approx(1 + 2 * np.pi) assert a.bin(2)[1] == approx(1 + 3 * np.pi) with pytest.raises(IndexError): a.bin(-1) # no underflow with pytest.raises(IndexError): a.bin(3) def test_iter(self): a = bh.axis.Regular(2, 1, 2, circular=True) ref = [(1, 1.5), (1.5, 2)] assert_allclose(a, ref) def test_index(self): a = bh.axis.Regular(4, 1, 1 + np.pi * 2, circular=True) d = 0.5 * np.pi assert a.index(0.99 - 4 * d) == 3 assert a.index(0.99 - 3 * d) == 0 assert a.index(0.99 - 2 * d) == 1 assert a.index(0.99 - d) == 2 assert a.index(0.99) == 3 assert a.index(1.0) == 0 assert a.index(1.01) == 0 assert a.index(0.99 + d) == 0 assert a.index(1.0 + d) == 1 assert a.index(1.0 + 2 * d) == 2 assert a.index(1.0 + 3 * d) == 3 assert a.index(1.0 + 4 * d) == 0 assert a.index(1.0 + 5 * d) == 1 def test_edges_centers_widths(self): a = bh.axis.Regular(2, 0, 1, circular=True) assert_allclose(a.edges, [0, 0.5, 1]) assert_allclose(a.centers, [0.25, 0.75]) assert_allclose(a.widths, [0.5, 0.5]) class TestVariable(Axis): def test_init(self): # should not raise bh.axis.Variable([0, 1]) bh.axis.Variable((0, 1, 2, 3, 4)) bh.axis.Variable([0, 1], metadata="va") with pytest.raises(TypeError): bh.axis.Variable() with pytest.raises(ValueError): bh.axis.Variable([1]) with pytest.raises(TypeError): bh.axis.Variable(1) with pytest.raises(ValueError): bh.axis.Variable([1, -1]) with pytest.raises(ValueError): bh.axis.Variable([1, 1]) with pytest.raises(TypeError): bh.axis.Variable(["1", 2]) with pytest.raises(TypeError): bh.axis.Variable([0.0, 1.0, 2.0], bad_keyword="ra") def test_traits(self): STD_TRAITS = {"continuous": True, "ordered": True} ax = bh.axis.Variable([1, 2, 3]) assert isinstance(ax, bh.axis.Variable) assert ax.traits == bh.axis.Traits(underflow=True, overflow=True, **STD_TRAITS) ax = bh.axis.Variable([1, 2, 3], overflow=False) assert isinstance(ax, bh.axis.Variable) assert ax.traits == bh.axis.Traits(underflow=True, **STD_TRAITS) ax = bh.axis.Variable([1, 2, 3], underflow=False) assert isinstance(ax, bh.axis.Variable) assert ax.traits == bh.axis.Traits(overflow=True, **STD_TRAITS) ax = bh.axis.Variable([1, 2, 3], underflow=False, overflow=False) assert isinstance(ax, bh.axis.Variable) assert ax.traits == bh.axis.Traits(**STD_TRAITS) def test_equal(self): a = bh.axis.Variable([-0.1, 0.2, 0.3]) assert a == bh.axis.Variable((-0.1, 0.2, 0.3)) assert a != bh.axis.Variable([0, 0.2, 0.3]) assert a != bh.axis.Variable([-0.1, 0.1, 0.3]) assert a != bh.axis.Variable([-0.1, 0.1]) def test_len(self): a = bh.axis.Variable([-0.1, 0.2, 0.3]) assert len(a) == 2 assert a.size == 2 assert a.extent == 4 def test_repr(self): a = bh.axis.Variable([-0.1, 0.2]) assert repr(a) == "Variable([-0.1, 0.2])" a = bh.axis.Variable([-0.1, 0.2], metadata="hi") assert repr(a) == "Variable([-0.1, 0.2], metadata='hi')" def test_getitem(self): ref = [-0.1, 0.2, 0.3] a = bh.axis.Variable(ref) for i in range(2): assert_allclose(a.bin(i), ref[i : i + 2]) assert_allclose(a[i], ref[i : i + 2]) assert a[-1] == a[1] with pytest.raises(IndexError): a[2] assert_allclose(a[bh.underflow], a.bin(-1)) assert_allclose(a[bh.overflow], a.bin(2)) assert a.bin(-1)[0] == -np.inf assert a.bin(-1)[1] == ref[0] assert a.bin(2)[0] == ref[2] assert a.bin(2)[1] == np.inf with pytest.raises(IndexError): a.bin(-2) with pytest.raises(IndexError): a.bin(3) def test_iter(self): ref = [-0.1, 0.2, 0.3] a = bh.axis.Variable(ref) for i, bin in enumerate(a): assert_array_equal(bin, ref[i : i + 2]) def test_index(self): a = bh.axis.Variable([-0.1, 0.2, 0.3]) assert a.index(-10.0) == -1 assert a.index(-0.11) == -1 assert a.index(-0.1) == 0 assert a.index(0.0) == 0 assert a.index(0.19) == 0 assert a.index(0.2) == 1 assert a.index(0.21) == 1 assert a.index(0.29) == 1 assert a.index(0.3) == 2 assert a.index(0.31) == 2 assert a.index(10) == 2 def test_edges_centers_widths(self): a = bh.axis.Variable([0, 1, 3]) assert_allclose(a.edges, [0, 1, 3]) assert_allclose(a.centers, [0.5, 2]) assert_allclose(a.widths, [1, 2]) class TestInteger: def test_init(self): bh.axis.Integer(-1, 2) bh.axis.Integer(-1, 2, metadata="foo") bh.axis.Integer(-1, 2, underflow=False) bh.axis.Integer(-1, 2, underflow=False, overflow=False) bh.axis.Integer(-1, 2, growth=True) with pytest.raises(TypeError): bh.axis.Integer() with pytest.raises(TypeError): bh.axis.Integer(1) with pytest.raises(TypeError): bh.axis.Integer("1", 2) with pytest.raises(ValueError): bh.axis.Integer(2, -1) with pytest.raises(TypeError): bh.axis.Integer(-1, 2, "foo") with pytest.raises(TypeError): bh.axis.Integer(20, 30, 40) def test_traits(self): STD_TRAITS = {"ordered": True} ax = bh.axis.Integer(1, 3) assert isinstance(ax, bh.axis.Integer) assert ax.traits == bh.axis.Traits(underflow=True, overflow=True, **STD_TRAITS) # See https://github.com/boostorg/histogram/issues/305 ax = bh.axis.Integer(1, 3, overflow=False) assert isinstance(ax, bh.axis.Integer) assert ax.traits == bh.axis.Traits(underflow=True, **STD_TRAITS) # See https://github.com/boostorg/histogram/issues/305 ax = bh.axis.Integer(1, 3, underflow=False) assert isinstance(ax, bh.axis.Integer) assert ax.traits == bh.axis.Traits(overflow=True, **STD_TRAITS) ax = bh.axis.Integer(1, 3, underflow=False, overflow=False) assert isinstance(ax, bh.axis.Integer) assert ax.traits == bh.axis.Traits(**STD_TRAITS) ax = bh.axis.Integer(1, 3, growth=True) assert isinstance(ax, bh.axis.Integer) assert ax.traits == bh.axis.Traits(growth=True, **STD_TRAITS) def test_equal(self): assert bh.axis.Integer(-1, 2) == bh.axis.Integer(-1, 2) assert bh.axis.Integer(-1, 2) != bh.axis.Integer(-1, 2, metadata="Other") assert bh.axis.Integer(-1, 2, underflow=True) != bh.axis.Integer( -1, 2, underflow=False ) def test_len(self, underflow, overflow): a = bh.axis.Integer(-1, 3, underflow=underflow, overflow=overflow) assert len(a) == 4 assert a.size == 4 assert a.extent == 4 + underflow + overflow def test_repr(self): a = bh.axis.Integer(-1, 1) assert repr(a) == "Integer(-1, 1)" a = bh.axis.Integer(-1, 1, metadata="hi") assert repr(a) == "Integer(-1, 1, metadata='hi')" a = bh.axis.Integer(-1, 1, metadata=2) assert repr(a) == "Integer(-1, 1, metadata=...)" a = bh.axis.Integer(-1, 1, underflow=False) assert repr(a) == "Integer(-1, 1, underflow=False)" a = bh.axis.Integer(-1, 1, overflow=False) assert repr(a) == "Integer(-1, 1, overflow=False)" a = bh.axis.Integer(-1, 1, growth=True) assert repr(a) == "Integer(-1, 1, growth=True)" def test_label(self): a = bh.axis.Integer(-1, 2, metadata="foo") assert a.metadata == "foo" a.metadata = "bar" assert a.metadata == "bar" def test_getitem(self): a = bh.axis.Integer(-1, 3) ref = [-1, 0, 1, 2] for i, r in enumerate(ref): assert a.bin(i) == r assert a[i] == r assert a.bin(-1) == -2 assert a.bin(4) == 3 assert_allclose(a[bh.underflow], a.bin(-1)) assert_allclose(a[bh.overflow], a.bin(4)) def test_iter(self): a = bh.axis.Integer(-1, 3) ref = (-1, 0, 1, 2) assert_array_equal(a, ref) def test_index(self): a = bh.axis.Integer(-1, 3) assert a.index(-3) == -1 assert a.index(-2) == -1 assert a.index(-1) == 0 assert a.index(0) == 1 assert a.index(1) == 2 assert a.index(2) == 3 assert a.index(3) == 4 assert a.index(4) == 4 def test_edges_centers_widths(self): a = bh.axis.Integer(1, 3) assert_allclose(a.edges, [1, 2, 3]) assert_allclose(a.centers, [1.5, 2.5]) assert_allclose(a.widths, [1, 1]) class TestCategory(Axis): def test_init(self): # should not raise bh.axis.IntCategory([1, 2]) bh.axis.IntCategory({1, 2}) bh.axis.IntCategory((1, 2), metadata="foo") bh.axis.StrCategory(["A", "B"]) bh.axis.StrCategory({"A", "B"}) bh.axis.StrCategory("AB") bh.axis.StrCategory("AB", metadata="foo") with pytest.raises(TypeError): bh.axis.IntCategory(["A", "B"]) with pytest.raises(TypeError): bh.axis.StrCategory([1, 2]) with pytest.raises(TypeError): bh.axis.IntCategory([1, 2], "foo") with pytest.raises(TypeError): bh.axis.StrCategory("AB", "foo") with pytest.raises(TypeError): bh.axis.StrCategory() with pytest.raises(TypeError): bh.axis.IntCategory() with pytest.raises(TypeError): bh.axis.StrCategory([1, "2"]) with pytest.raises(TypeError): bh.axis.IntCategory([1, "2"]) with pytest.raises(TypeError): bh.axis.IntCategory([1, 2, 3], underflow=True) def test_traits(self): ax = bh.axis.IntCategory([1, 2, 3]) assert isinstance(ax, bh.axis.IntCategory) assert ax.traits == bh.axis.Traits(overflow=True) ax = bh.axis.IntCategory([1, 2, 3], growth=True) assert isinstance(ax, bh.axis.IntCategory) assert ax.traits == bh.axis.Traits(growth=True) ax = bh.axis.StrCategory(["1", "2", "3"]) assert isinstance(ax, bh.axis.StrCategory) assert ax.traits == bh.axis.Traits(overflow=True) ax = bh.axis.StrCategory(["1", "2", "3"], growth=True) assert isinstance(ax, bh.axis.StrCategory) assert ax.traits == bh.axis.Traits(growth=True) def test_equal(self): assert bh.axis.IntCategory([1, 2, 3]) == bh.axis.IntCategory([1, 2, 3]) assert bh.axis.IntCategory([1, 2, 3]) != bh.axis.IntCategory([1, 3, 2]) assert bh.axis.StrCategory(["A", "B"]) == bh.axis.StrCategory("AB") assert bh.axis.StrCategory(["A", "B"]) != bh.axis.StrCategory("BA") @pytest.mark.parametrize("ref", [[1, 2, 3], "ABC"]) def test_len(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) assert len(a) == 3 assert a.size == 3 assert a.extent == 3 if growth else 4 def test_repr(self): ax = bh.axis.IntCategory([1, 2, 3]) assert repr(ax) == "IntCategory([1, 2, 3])" ax = bh.axis.IntCategory([1, 2, 3], metadata="foo") assert repr(ax) == "IntCategory([1, 2, 3], metadata='foo')" ax = bh.axis.StrCategory("ABC", metadata="foo") assert repr(ax) == "StrCategory(['A', 'B', 'C'], metadata='foo')" @pytest.mark.parametrize("ref", [[1, 2, 3], "ABC"]) def test_getitem(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) for i in range(3): assert a.bin(i) == ref[i] assert a[i] == ref[i] assert a[-1] == a[2] with pytest.raises(IndexError): a[3] with pytest.raises(IndexError): a.bin(-1) if growth: with pytest.raises(IndexError): a.bin(3) else: assert a.bin(3) is None @pytest.mark.parametrize("ref", [[1, 2, 3], ("A", "B", "C")]) def test_iter(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) assert_array_equal(a, ref) @pytest.mark.parametrize( "ref", [[1, 2, 3, 4], ("A", "B", "C", "D")], ids=("int", "str") ) def test_index(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) for i, r in enumerate(ref): assert a.index(r) == i assert_array_equal(a.index(ref), [0, 1, 2, 3]) assert_array_equal(a.index(np.reshape(ref, (2, 2))), [[0, 1], [2, 3]]) if isinstance(ref[0], str): with pytest.raises(KeyError): a.index("E") else: with pytest.raises(KeyError): a.index(5) with pytest.raises(KeyError): a.index(-2) @pytest.mark.parametrize("ref", [[1, 2, 3], ("A", "B", "C")]) def test_value(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) for i, r in enumerate(ref): assert a.value(i) == r assert_array_equal(a.value(range(3)), ref) assert a.value(3) is None assert_array_equal(a.value((0, 3)), [ref[0], None]) assert_array_equal( a.value(np.array((0, 1, 2, 3))), [ref[0], ref[1], ref[2], None] ) # may be added in the future with pytest.raises(ValueError): a.value([[2], [2]]) @pytest.mark.parametrize("ref", [[1, 2, 3], "ABC"]) def test_edges_centers_widths(self, ref, growth): Cat = bh.axis.StrCategory if isinstance(ref[0], str) else bh.axis.IntCategory a = Cat(ref, growth=growth) assert_allclose(a.edges, [0, 1, 2, 3]) assert_allclose(a.centers, [0.5, 1.5, 2.5]) assert_allclose(a.widths, [1, 1, 1]) class TestBoolean: def test_init(self): bh.axis.Boolean() bh.axis.Boolean(metadata="foo") with pytest.raises(TypeError): bh.axis.Boolean(1) def test_traits(self): ax = bh.axis.Boolean() assert isinstance(ax, bh.axis.Boolean) assert ax.traits == bh.axis.Traits(ordered=True) def test_equal(self): assert bh.axis.Boolean() == bh.axis.Boolean() assert bh.axis.Boolean(metadata="hi") == bh.axis.Boolean(metadata="hi") assert bh.axis.Boolean(metadata="hi") != bh.axis.Boolean() assert bh.axis.Boolean(metadata="hi") != bh.axis.Boolean(metadata="ho") def test_len(self): a = bh.axis.Boolean() assert len(a) == 2 assert a.size == 2 assert a.extent == 2 def test_repr(self): a = bh.axis.Boolean() assert repr(a) == "Boolean()" a = bh.axis.Boolean(metadata="hi") assert repr(a) == "Boolean(metadata='hi')" def test_label(self): a = bh.axis.Boolean(metadata="foo") assert a.metadata == "foo" a.metadata = "bar" assert a.metadata == "bar" def test_getitem(self): a = bh.axis.Boolean() ref = [False, True] for i, r in enumerate(ref): assert a.bin(i) == r assert a[i] == r assert a.bin(0) == 0 assert a.bin(1) == 1 def test_iter(self): a = bh.axis.Boolean() ref = (False, True) assert_array_equal(a, ref) def test_index(self): a = bh.axis.Boolean() assert a.index(False) == 0 assert a.index(True) == 1 def test_edges_centers_widths(self): a = bh.axis.Boolean() assert_allclose(a.edges, [0.0, 1.0, 2.0]) assert_allclose(a.centers, [0.5, 1.5]) assert_allclose(a.widths, [1, 1]) scikit-hep-boost-histogram-5f6f3e3/tests/test_axis_options.py000066400000000000000000000016371516627434300246300ustar00rootroot00000000000000from __future__ import annotations from boost_histogram.axis import Traits def test_compare(): assert Traits() == Traits() assert Traits(underflow=True) == Traits(underflow=True) assert Traits(overflow=True) == Traits(overflow=True) assert Traits(circular=True) == Traits(circular=True) assert Traits(growth=True) == Traits(growth=True) assert Traits(underflow=True, overflow=True) == Traits( underflow=True, overflow=True ) assert Traits() != Traits(overflow=True) assert Traits(underflow=True) != Traits(overflow=True) assert Traits(underflow=True, overflow=True) != Traits(overflow=True) def test_fields(): o = Traits() assert not o.underflow assert not o.overflow assert not o.circular assert not o.growth o = Traits(underflow=True, overflow=True) assert o.underflow assert o.overflow assert not o.circular assert not o.growth scikit-hep-boost-histogram-5f6f3e3/tests/test_benchmark_1d.py000066400000000000000000000030721516627434300244220ustar00rootroot00000000000000from __future__ import annotations import platform import numpy as np import pytest from numpy.testing import assert_allclose, assert_array_equal import boost_histogram as bh STORAGES = (bh.storage.Int64, bh.storage.Double, bh.storage.Unlimited) DTYPES = (np.float64, np.float32, np.int64, np.int32) # Casting is broken for numpy on ppc64le: https://github.com/numpy/numpy/issues/21062 if platform.machine() == "ppc64le": DTYPES = (np.float64, np.int64) bins = 100 ranges = (-1, 1) bins = np.asarray(bins).astype(np.int64) ranges = np.asarray(ranges).astype(np.float64) edges = np.linspace(ranges[0], ranges[1], bins + 1) np.random.seed(42) vals_core = np.random.normal(size=[100000]) vals = {t: vals_core.astype(t) for t in DTYPES} answer = {t: np.histogram(vals[t], bins=bins, range=ranges)[0] for t in DTYPES} @pytest.mark.benchmark(group="1d-fills") @pytest.mark.parametrize("dtype", vals) def test_numpy_1d(benchmark, dtype): result, _ = benchmark(np.histogram, vals[dtype], bins=bins, range=ranges) assert_array_equal(result, answer[dtype]) def make_and_run_hist(flow, storage, vals): histo = bh.Histogram( bh.axis.Regular(bins, *ranges, underflow=flow, overflow=flow), storage=storage() ) histo.fill(vals) return histo.view() @pytest.mark.benchmark(group="1d-fills") @pytest.mark.parametrize("dtype", vals) @pytest.mark.parametrize("storage", STORAGES) def test_boost_1d(benchmark, flow, storage, dtype): result = benchmark(make_and_run_hist, flow, storage, vals[dtype]) assert_allclose(result[:-1], answer[dtype][:-1], atol=2) scikit-hep-boost-histogram-5f6f3e3/tests/test_benchmark_2d.py000066400000000000000000000033641516627434300244270ustar00rootroot00000000000000from __future__ import annotations import platform import numpy as np import pytest from numpy.testing import assert_array_equal import boost_histogram as bh STORAGES = (bh.storage.Int64, bh.storage.Double, bh.storage.Unlimited) DTYPES = (np.float64, np.float32, np.int64, np.int32) # Casting is broken for numpy on ppc64le: https://github.com/numpy/numpy/issues/21062 if platform.machine() == "ppc64le": DTYPES = (np.float64, np.int64) bins = (100, 100) ranges = ((-1, 1), (-1, 1)) bins = np.asarray(bins).astype(np.int64) ranges = np.asarray(ranges).astype(np.float64) edges = ( np.linspace(ranges[0, 0], ranges[0, 1], bins[0] + 1), np.linspace(ranges[1, 0], ranges[1, 1], bins[1] + 1), ) np.random.seed(42) vals_core = np.random.normal(size=[2, 100000]) vals = {t: vals_core.astype(t) for t in DTYPES} answer = {t: np.histogram2d(*vals[t], bins=bins, range=ranges)[0] for t in DTYPES} @pytest.mark.benchmark(group="2d-fills") @pytest.mark.parametrize("dtype", vals) def test_numpy_perf_2d(benchmark, dtype): result, _, _ = benchmark(np.histogram2d, *vals[dtype], bins=bins, range=ranges) assert_array_equal(result, answer[dtype]) def make_and_run_hist(flow, storage, vals): histo = bh.Histogram( bh.axis.Regular(bins[0], *ranges[0], underflow=flow, overflow=flow), bh.axis.Regular(bins[1], *ranges[1], underflow=flow, overflow=flow), storage=storage(), ) histo.fill(*vals) return histo.view() @pytest.mark.benchmark(group="2d-fills") @pytest.mark.parametrize("dtype", vals) @pytest.mark.parametrize("storage", STORAGES) def test_2d(benchmark, flow, storage, dtype): result = benchmark(make_and_run_hist, flow, storage, vals[dtype]) assert_array_equal(result[:-1, :-1], answer[dtype][:-1, :-1]) scikit-hep-boost-histogram-5f6f3e3/tests/test_benchmark_category_axis.py000066400000000000000000000036751516627434300267700ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest import boost_histogram as bh @pytest.mark.benchmark(group="IntCategory") @pytest.mark.parametrize("dtype", ["i", tuple]) def test_IntCategory(benchmark, growth, dtype): np.random.seed(42) values = np.random.choice(np.arange(5), size=[100000]) h = bh.Histogram( bh.axis.IntCategory([] if growth else np.arange(4), growth=growth), storage=bh.storage.Double(), ) def run(h, data): h.fill(data) benchmark(run, h, tuple(values) if dtype is tuple else values.astype(dtype)) @pytest.mark.benchmark(group="StrCategory") @pytest.mark.parametrize("dtype", ["S", "U", "O", tuple]) def test_StrCategory(benchmark, growth, dtype): np.random.seed(42) values = np.random.choice(["A", "B", "C", "D", "E"], size=[100000]) h = bh.Histogram( bh.axis.StrCategory([] if growth else ["A", "B", "C", "D"], growth=growth), storage=bh.storage.Double(), ) def run(h, data): h.fill(data) benchmark(run, h, tuple(values) if dtype is tuple else values.astype(dtype)) @pytest.mark.benchmark(group="Pick") def test_pick_only(benchmark): h = bh.Histogram( bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.Regular(32, 0, 320), ) h[...] = 1.0 def run(h): return h[bh.loc("13"), bh.loc("13"), bh.loc("13"), :].view() benchmark(run, h) @pytest.mark.benchmark(group="Pick") def test_pick_and_slice(benchmark): h = bh.Histogram( bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.StrCategory([str(i) for i in range(32)]), bh.axis.Regular(32, 0, 320), ) h[...] = 1.0 def run(h): return h[3:29, bh.loc("13"), bh.loc("13"), :].view() benchmark(run, h) scikit-hep-boost-histogram-5f6f3e3/tests/test_histogram.py000066400000000000000000001137171516627434300241110ustar00rootroot00000000000000from __future__ import annotations import copy import functools import operator import pickle import platform import sys import threading from collections import OrderedDict from io import BytesIO import numpy as np import pytest from numpy.testing import assert_array_equal from pytest import approx import boost_histogram as bh def test_init(): bh.Histogram() bh.Histogram(bh.axis.Integer(-1, 1)) with pytest.raises(TypeError): bh.Histogram(1) with pytest.raises(TypeError): bh.Histogram("bla") with pytest.raises(TypeError): bh.Histogram([]) with pytest.raises(TypeError): bh.Histogram(bh.axis.Regular) with pytest.raises(TypeError): bh.Histogram(bh.axis.Regular()) with pytest.raises(TypeError): bh.Histogram([bh.axis.Integer(-1, 1)]) with pytest.raises(TypeError): bh.Histogram([bh.axis.Integer(-1, 1), bh.axis.Integer(-1, 1)]) with pytest.raises(TypeError): bh.Histogram(bh.axis.Integer(-1, 1), unknown_keyword="nh") h = bh.Histogram(bh.axis.Integer(-1, 2)) assert h.ndim == 1 assert h.axes[0] == bh.axis.Integer(-1, 2) assert h.axes[0].extent == 5 assert h.axes[0].size == 3 assert h != bh.Histogram(bh.axis.Regular(1, -1, 1)) assert h != bh.Histogram(bh.axis.Integer(-1, 1, metadata="ia")) def test_copy(): a = bh.Histogram(bh.axis.Integer(-1, 1)) b = copy.copy(a) assert a == b assert id(a) != id(b) c = copy.deepcopy(b) assert b == c assert id(b) != id(c) b = a.copy(deep=False) assert a == b assert id(a) != id(b) c = a.copy() assert b == c assert id(b) != id(c) def test_fill_int_1d(): h = bh.Histogram(bh.axis.Integer(-1, 2)) assert isinstance(h, bh.Histogram) assert h.empty() assert h.empty(flow=True) with pytest.raises(ValueError): h.fill() with pytest.raises(ValueError): h.fill(1, 2) with pytest.raises(TypeError): h.fill(1, fiddlesticks=2) h.fill(-3) assert h.empty() assert not h.empty(flow=True) h.reset() for x in (-10, -1, -1, 0, 1, 1, 1, 10): h.fill(x) assert h.sum() == 6 assert not h.empty() assert not h.empty(flow=True) assert h.sum(flow=True) == 8 assert h.axes[0].extent == 5 with pytest.raises(IndexError): h[0, 1] for get in (lambda h, arg: h[arg], lambda h, arg: h[arg]): # lambda h, arg: h[arg]): assert get(h, 0) == 2 assert get(h, 1) == 1 assert get(h, 2) == 3 # assert get(h, 0).variance == 2 # assert get(h, 1).variance == 1 # assert get(h, 2).variance == 3 assert h[bh.overflow - 1] == 3 assert h[bh.overflow] == 1 assert h[bh.underflow] == 1 assert h[bh.underflow + 1] == 2 assert h[-1] == 3 with pytest.raises(IndexError): h[3] with pytest.raises(IndexError): h[-3] def test_fill_int_with_float_single_1d(): h = bh.Histogram(bh.axis.Integer(-1, 2)) with pytest.raises(TypeError): h.fill(0.3) def test_fill_int_with_float_array_1d(): h = bh.Histogram(bh.axis.Integer(-1, 2)) with pytest.raises(TypeError): h.fill([-0.3, 0.3]) def test_fill_1d(flow): h = bh.Histogram(bh.axis.Regular(3, -1, 2, underflow=flow, overflow=flow)) with pytest.raises(ValueError): h.fill() with pytest.raises(ValueError): h.fill(1, 2) for x in (-10, -1, -1, 0, 1, 1, 1, 10): h.fill(x) assert h.sum() == 6 assert h.sum(flow=True) == 6 + 2 * flow assert h.axes[0].extent == 3 + 2 * flow with pytest.raises(IndexError): h[0, 1] for get in (lambda h, arg: h[arg],): # lambda h, arg: h[arg]): assert get(h, 0) == 2 assert get(h, 1) == 1 assert get(h, 2) == 3 # assert get(h, 0).variance == 2 # assert get(h, 1).variance == 1 # assert get(h, 2).variance == 3 if flow is True: assert get(h, bh.underflow) == 1 assert get(h, bh.overflow) == 1 def test_setting(count_single_storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=count_single_storage()) h[bh.underflow] = 1 h[0] = 2 h[1] = 3 h[bh.loc(0.55)] = 4 h[-1] = 5 h[bh.overflow] = 6 assert h[bh.underflow] == 1 assert h[0] == 2 assert h[1] == 3 assert h[bh.loc(0.55)] == 4 assert h[5] == 4 assert h[-1] == 5 assert h[9] == 5 assert h[bh.overflow] == 6 assert_array_equal(h.view(flow=True), [1, 2, 3, 0, 0, 0, 4, 0, 0, 0, 5, 6]) def test_growth(): h = bh.Histogram(bh.axis.Integer(-1, 2)) h.fill(-1) h.fill(1) h.fill(1) for _ in range(255): h.fill(0) h.fill(0) for _ in range(1000 - 256): h.fill(0) print(h.view(flow=True)) assert h[bh.underflow] == 0 assert h[0] == 1 assert h[1] == 1000 assert h[2] == 2 assert h[bh.overflow] == 0 def test_growing_cats(): h = bh.Histogram( bh.axis.IntCategory([], growth=True), bh.axis.StrCategory([], growth=True) ) h.fill([1, 2, 1, 1], ["hi", "ho", "hi", "ho"]) assert h.size == 4 def test_noflow_cats(): h = bh.Histogram( bh.axis.IntCategory([1, 2, 3], overflow=False), bh.axis.StrCategory(["hi"], overflow=False), ) h.fill([1, 2, 3, 4], ["hi", "ho", "hi", "ho"]) assert h.sum() == 2 def test_metadata_add(): h1 = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), bh.axis.StrCategory(["1", "2", "3"]) ) h2 = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), bh.axis.StrCategory(["1", "2", "3"]) ) h1.fill([1, 2, 1, 2], ["1", "1", "2", "2"]) h2.fill([2, 3, 2, 3], ["2", "2", "3", "3"]) h3 = h1 + h2 assert h1.axes[0].size == 3 assert h2.axes[0].size == 3 assert h3.axes[0].size == 3 assert h1.axes[1].size == 3 assert h2.axes[1].size == 3 assert h3.axes[1].size == 3 assert h3[bh.loc(2), bh.loc("2")] == 2.0 def test_grow_and_add(): h1 = bh.Histogram( bh.axis.IntCategory([], growth=True), bh.axis.StrCategory([], growth=True) ) h2 = bh.Histogram( bh.axis.IntCategory([], growth=True), bh.axis.StrCategory([], growth=True) ) h1.fill([1, 2, 1, 2], ["hi", "hi", "ho", "ho"]) h2.fill([2, 3, 4, 5], ["ho", "ho", "hum", "hum"]) h3 = h1 + h2 assert h1.axes[0].size == 2 assert h2.axes[0].size == 4 assert h3.axes[0].size == 5 assert h1.axes[1].size == 2 assert h2.axes[1].size == 2 assert h3.axes[1].size == 3 assert h3[bh.loc(2), bh.loc("ho")] == 2.0 def test_fill_2d(flow): h = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), ) h.fill(-1, -2) h.fill(-1, -1) h.fill(0, 0) h.fill(0, 1) h.fill(1, 0) h.fill(3, -1) h.fill(0, -3) # TODO: this really should be a TypeError, but we are throwing ValueError with pytest.raises(ValueError): h.fill(1) with pytest.raises(ValueError): h.fill(1, 2, 3) m = [ [1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], ] for get in (lambda h, x, y: h[bh.tag.at(x), bh.tag.at(y)],): # lambda h, x, y: h[x, y]): for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert get(h, i, j) == m[i][j] h.fill(1, [1, 2]) h.fill(np.float64(1), [1, 2]) def test_add_2d(flow): h0 = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), ) assert isinstance(h0, bh.Histogram) h0.fill(-1, -2) h0.fill(-1, -1) h0.fill(0, 0) h0.fill(0, 1) h0.fill(1, 0) h0.fill(3, -1) h0.fill(0, -3) m = [ [1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], ] h = h0.copy() h += h for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert h[bh.tag.at(i), bh.tag.at(j)] == 2 * m[i][j] h = sum([h0, h0]) for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert h[bh.tag.at(i), bh.tag.at(j)] == 2 * m[i][j] h = 0 + h0 + h0 for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert h[bh.tag.at(i), bh.tag.at(j)] == 2 * m[i][j] @pytest.mark.parametrize("flow", [True, False]) def test_add_2d_fancy(flow): h = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), ) assert isinstance(h, bh.Histogram) h.fill(-1, -2) h.fill(-1, -1) h.fill(0, 0) h.fill(0, 1) h.fill(1, 0) h.fill(3, -1) h.fill(0, -3) m = [ [1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], ] h += h for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert h[bh.tag.at(i), bh.tag.at(j)] == 2 * m[i][j] def test_add_2d_bad(): a = bh.Histogram(bh.axis.Integer(-1, 1)) b = bh.Histogram(bh.axis.Regular(3, -1, 1)) with pytest.raises(ValueError): a += b def test_add_2d_w(flow): h = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), ) h.fill(-1, -2) h.fill(-1, -1) h.fill(0, 0) h.fill(0, 1) h.fill(1, 0) h.fill(3, -1) h.fill(0, -3) m = [ [1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], ] h2 = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), ) h2.fill(0, 0, weight=0) h2 += h h2 += h h += h assert h == h2 for i in range(-flow, h.axes[0].size + flow): for j in range(-flow, h.axes[1].size + flow): assert h[bh.tag.at(i), bh.tag.at(j)] == 2 * m[i][j] def test_sub_2d(flow, count_storage): h0 = bh.Histogram( bh.axis.Integer(-1, 2, underflow=flow, overflow=flow), bh.axis.Regular(4, -2, 2, underflow=flow, overflow=flow), storage=count_storage(), ) h0.fill(-1, -2) h0.fill(-1, -1) h0.fill(0, 0) h0.fill(0, 1) h0.fill(1, 0) h0.fill(3, -1) h0.fill(0, -3) m = h0.values(flow=True).copy() if count_storage not in {bh.storage.AtomicInt64, bh.storage.Weight}: h = h0.copy() h -= h0 assert h.values(flow=True) == approx(m * 0) h -= h0 assert h.values(flow=True) == approx(-m) h2 = h0 - (h0 + h0 + h0) assert h2.values(flow=True) == approx(-2 * m) h3 = h0 - h0.view(flow=True) * 4 assert h3.values(flow=True) == approx(-3 * m) h4 = h0.copy() h4 -= h0.view(flow=True) * 5 assert h4.values(flow=True) == approx(-4 * m) h5 = h0.copy() h5 -= 2 assert h5.values(flow=True) == approx(m - 2) h6 = h0 - 3 assert h6.values(flow=True) == approx(m - 3) def test_repr(): hrepr = """Histogram( Regular(3, 0, 1), Integer(0, 1), storage=Double())""" h = bh.Histogram(bh.axis.Regular(3, 0, 1), bh.axis.Integer(0, 1)) assert repr(h) == hrepr h.fill([0.3, 0.5], [0, 0]) hrepr += " # Sum: 2.0" assert repr(h) == hrepr h.fill([0.3, 12], [3, 0]) hrepr += " (4.0 with flow)" assert repr(h) == hrepr def test_1d_repr(): hrepr = """Histogram(Regular(4, 1, 2), storage=Double())""" h = bh.Histogram(bh.axis.Regular(4, 1, 2)) assert repr(h) == hrepr def test_empty_repr(): hrepr = """Histogram(storage=Double())""" h = bh.Histogram() assert repr(h) == hrepr def test_str(): h1 = bh.Histogram(bh.axis.Regular(3, 0, 1)) h1.view(True)[...] = [0, 1, 3, 2, 1] current_repr = repr(str(h1)) assert "[ -inf, 0)" in current_repr assert "[0.3333, 0.6667) 3" in current_repr assert "[ 1, inf)" in current_repr h2 = bh.Histogram(bh.axis.Regular(3, 0, 1), bh.axis.Integer(0, 1)) assert repr(str(h2)) == repr(repr(h2)) def test_axis(): axes = (bh.axis.Regular(10, 0, 1), bh.axis.Integer(0, 1)) h = bh.Histogram(*axes) for i, a in enumerate(axes): assert h.axes[i] == a with pytest.raises(IndexError): h.axes[2] assert h.axes[-1] == axes[-1] assert h.axes[-2] == axes[-2] with pytest.raises(IndexError): h.axes[-3] def test_out_of_limit_axis(): lim = bh._core.hist._axes_limit ax = ( bh.axis.Regular(1, -1, 1, underflow=False, overflow=False) for a in range(lim) ) # Nothrow bh.Histogram(*ax) ax = ( bh.axis.Regular(1, -1, 1, underflow=False, overflow=False) for a in range(lim + 1) ) with pytest.raises(IndexError): bh.Histogram(*ax) def test_out_of_range(): h = bh.Histogram(bh.axis.Regular(3, 0, 1)) h.fill(-1) h.fill(2) assert h[bh.underflow] == 1 assert h[bh.overflow] == 1 with pytest.raises(IndexError): h[4] def test_operators(): h = bh.Histogram(bh.axis.Integer(0, 2)) h.fill(0) h_orig = h h += h assert h is h_orig assert h[0] == 2 assert h[1] == 0 h *= 2 assert h is h_orig assert h[0] == 4 assert h[1] == 0 assert (h + h)[0] == (h * 2)[0] assert (h + h)[0] == (2 * h)[0] h /= 2 assert h is h_orig assert h[0] == 2 assert h[1] == 0 assert (h / 2)[0] == 1 h2 = bh.Histogram(bh.axis.Regular(2, 0, 2)) with pytest.raises(ValueError): h + h2 def test_hist_hist_div(): h1 = bh.Histogram(bh.axis.Boolean()) h2 = bh.Histogram(bh.axis.Boolean()) h1[:] = (8, 6) h2[:] = (2, 3) h3 = h1 / h2 assert h3[False] == 4 assert h3[True] == 2 h1 /= h2 assert h1[False] == 4 assert h1[True] == 2 def test_project(): h = bh.Histogram(bh.axis.Integer(0, 2), bh.axis.Integer(1, 4)) h.fill(0, 1) h.fill(0, 2) h.fill(1, 3) h0 = h.project(0) assert h0.ndim == 1 assert h0.axes[0] == bh.axis.Integer(0, 2) assert [h0[i] for i in range(2)] == [2, 1] h1 = h.project(1) assert h1.ndim == 1 assert h1.axes[0] == bh.axis.Integer(1, 4) assert [h1[i] for i in range(3)] == [1, 1, 1] with pytest.raises(ValueError): h.project(*range(10)) with pytest.raises(ValueError): h.project(2, 1) with pytest.raises(ValueError): h.project(9) with pytest.raises(ValueError): h.project(-1) def test_shrink_1d(): h = bh.Histogram(bh.axis.Regular(20, 1, 5)) h.fill(1.1) hs = h[{0: slice(bh.loc(1), bh.loc(2))}] assert_array_equal(hs.view(), [1, 0, 0, 0, 0]) d = OrderedDict({0: slice(bh.loc(1), bh.loc(2))}) hs = h[d] assert_array_equal(hs.view(), [1, 0, 0, 0, 0]) @pytest.mark.parametrize( "metadata", [None, {}, {"a": "1"}], ids=["None", "empty", "dict"] ) def test_rebin_1d(metadata): h = bh.Histogram(bh.axis.Regular(20, 1, 5, metadata=metadata)) h.fill([1.1, 2.2, 3.3, 4.4]) hs = h[{0: slice(None, None, bh.rebin(4))}] assert_array_equal(hs.view(), [1, 1, 1, 0, 1]) assert h.axes[0].metadata is hs.axes[0].metadata hs = h[{0: bh.rebin(4)}] assert_array_equal(hs.view(), [1, 1, 1, 0, 1]) assert h.axes[0].metadata is hs.axes[0].metadata hs = h[{0: bh.rebin(groups=[1, 2, 3, 14])}] assert_array_equal(hs.view(), [1, 0, 0, 3]) assert_array_equal(hs.axes.edges[0], [1.0, 1.2, 1.6, 2.2, 5.0]) assert h.axes[0].metadata is hs.axes[0].metadata exact_edges = [1.0, 1.2, 1.6, 2.2, 5.0] hs = h[bh.rebin(edges=exact_edges)] assert_array_equal(hs.view(), [1, 0, 0, 3]) assert_array_equal(hs.axes.edges[0], exact_edges) assert h.axes[0].metadata is hs.axes[0].metadata fuzzy_edges = [1.0, 1.200000000000001, 1.6, 2.2, 5.0] hs = h[bh.rebin(edges=fuzzy_edges)] assert_array_equal(hs.view(), [1, 0, 0, 3]) assert_array_equal(hs.axes.edges[0], exact_edges) assert h.axes[0].metadata is hs.axes[0].metadata hs = h[bh.rebin(axis=bh.axis.Variable([1.0, 1.2, 1.6, 2.2, 5.0], metadata="hi"))] assert_array_equal(hs.view(), [1, 0, 0, 3]) assert_array_equal(hs.axes.edges[0], [1.0, 1.2, 1.6, 2.2, 5.0]) assert hs.axes[0].metadata == "hi" def test_rebin_1d_flow(): h = bh.Histogram(bh.axis.Regular(5, 0, 5, underflow=True, overflow=True)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[bh.rebin(edges=[0, 3, 5.0])] assert_array_equal(hs.view(), [2, 2]) assert_array_equal(hs.view(flow=True), [1, 2, 2, 1]) assert_array_equal(hs.axes.edges[0], [0.0, 3.0, 5.0]) # Flow bins are kept from the original h = bh.Histogram(bh.axis.Regular(5, 0, 5, underflow=False, overflow=False)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[bh.rebin(edges=[0, 3, 5.0])] assert_array_equal(hs.view(flow=True), [2, 2]) h = bh.Histogram(bh.axis.Regular(5, 0, 5, underflow=True, overflow=False)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[bh.rebin(edges=[0, 3, 5.0])] assert_array_equal(hs.view(flow=True), [1, 2, 2]) h = bh.Histogram(bh.axis.Regular(5, 0, 5, underflow=True, overflow=True)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[ bh.rebin(axis=bh.axis.Variable([0, 3, 5.0], underflow=False, overflow=False)) ] assert_array_equal(hs.view(flow=True), [2, 2]) def test_rebin_change_axis_int(): h = bh.Histogram(bh.axis.Regular(5, 0, 5)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[bh.rebin(edges=[0, 3, 5.0], axis=bh.axis.Integer(10, 12))] assert_array_equal(hs.view(), [2, 2]) assert_array_equal(hs.view(flow=True), [1, 2, 2, 1]) assert_array_equal(hs.axes.edges[0], [10, 11, 12]) def test_rebin_change_axis_cat(): h = bh.Histogram(bh.axis.Regular(5, 0, 5)) h.fill([-1, 1.1, 2.2, 3.3, 4.4, 5.5]) hs = h[bh.rebin(groups=[2, 3], axis=bh.axis.StrCategory(["a", "b"]))] assert_array_equal(hs.view(), [1, 3]) assert_array_equal(hs.view(flow=True), [1, 3, 4]) assert_array_equal(list(hs.axes[0]), ["a", "b"]) def test_shrink_rebin_1d(): h = bh.Histogram(bh.axis.Regular(20, 0, 4)) h.fill(1.1) hs = h[{0: slice(bh.loc(1), bh.loc(3), bh.rebin(2))}] assert_array_equal(hs.view(), [1, 0, 0, 0, 0]) def test_rebin_nd(): h = bh.Histogram( bh.axis.Regular(20, 1, 3), bh.axis.Regular(30, 1, 3), bh.axis.Regular(40, 1, 3) ) assert h[{0: np.s_[:: bh.rebin(2)]}].axes.size == (10, 30, 40) assert h[{1: np.s_[:: bh.rebin(2)]}].axes.size == (20, 15, 40) assert h[{2: np.s_[:: bh.rebin(2)]}].axes.size == (20, 30, 20) assert h[{0: np.s_[:: bh.rebin(groups=[1, 2, 17])]}].axes.size == (3, 30, 40) assert h[{1: np.s_[:: bh.rebin(groups=[1, 2, 27])]}].axes.size == (20, 3, 40) assert h[{2: np.s_[:: bh.rebin(groups=[1, 2, 37])]}].axes.size == (20, 30, 3) assert np.all( np.isclose( h[{0: np.s_[:: bh.rebin(groups=[1, 2, 17])]}].axes[0].edges, [1.0, 1.1, 1.3, 3.0], ) ) assert np.all( np.isclose( h[{1: np.s_[:: bh.rebin(groups=[1, 2, 27])]}].axes[1].edges, [1.0, 1.06666667, 1.2, 3.0], ) ) assert np.all( np.isclose( h[{2: np.s_[:: bh.rebin(groups=[1, 2, 37])]}].axes[2].edges, [1.0, 1.05, 1.15, 3.0], ) ) assert h[{0: np.s_[:: bh.rebin(2)], 2: np.s_[:: bh.rebin(2)]}].axes.size == ( 10, 30, 20, ) assert h[ { 0: np.s_[:: bh.rebin(groups=[1, 2, 17])], 2: np.s_[:: bh.rebin(groups=[1, 2, 37])], } ].axes.size == (3, 30, 3) assert np.all( np.isclose( h[ { 0: np.s_[:: bh.rebin(groups=[1, 2, 17])], 2: np.s_[:: bh.rebin(groups=[1, 2, 37])], } ] .axes[0] .edges, [1.0, 1.1, 1.3, 3], ) ) assert np.all( np.isclose( h[ { 0: np.s_[:: bh.rebin(groups=[1, 2, 17])], 2: np.s_[:: bh.rebin(groups=[1, 2, 37])], } ] .axes[2] .edges, [1.0, 1.05, 1.15, 3.0], ) ) assert h[{1: np.s_[:: bh.sum]}].axes.size == (20, 40) assert h[{1: bh.sum}].axes.size == (20, 40) # CLASSIC: This used to have metadata too, but that does not compare equal def test_pickle_0(flow): a = bh.Histogram( bh.axis.IntCategory([0, 1, 2], overflow=flow), bh.axis.Integer(0, 20), bh.axis.Regular(2, 0.0, 20.0, underflow=False, overflow=False), bh.axis.Variable([0.0, 1.0, 2.0]), bh.axis.Regular(4, 0, 2 * np.pi, circular=True), ) for i in range(a.axes[0].extent): a.fill(i, 0, 0, 0, 0) for j in range(a.axes[1].extent): a.fill(i, j, 0, 0, 0) for k in range(a.axes[2].extent): a.fill(i, j, k, 0, 0) for l in range(a.axes[3].extent): # noqa: E741 a.fill(i, j, k, l, 0) for m in range(a.axes[4].extent): a.fill(i, j, k, l, m * 0.5 * np.pi) io = pickle.dumps(a, -1) b = pickle.loads(io) assert id(a) != id(b) assert a.ndim == b.ndim assert a.axes[0] == b.axes[0] assert a.axes[1] == b.axes[1] assert a.axes[2] == b.axes[2] assert a.axes[3] == b.axes[3] assert a.axes[4] == b.axes[4] assert a.sum() == b.sum() assert repr(a) == repr(b) assert str(a) == str(b) assert a == b def test_pickle_1(): a = bh.Histogram( bh.axis.IntCategory([0, 1, 2]), bh.axis.Integer(0, 3, metadata="ia"), bh.axis.Regular(4, 0.0, 4.0, underflow=False, overflow=False), bh.axis.Variable([0.0, 1.0, 2.0]), ) assert isinstance(a, bh.Histogram) for i in range(a.axes[0].extent): a.fill(i, 0, 0, 0, weight=3) for j in range(a.axes[1].extent): a.fill(i, j, 0, 0, weight=10) for k in range(a.axes[2].extent): a.fill(i, j, k, 0, weight=2) for l in range(a.axes[3].extent): # noqa: E741 a.fill(i, j, k, l, weight=5) io = BytesIO() pickle.dump(a, io, protocol=-1) io.seek(0) b = pickle.load(io) assert id(a) != id(b) assert a.ndim == b.ndim assert a.axes[0] == b.axes[0] assert a.axes[1] == b.axes[1] assert a.axes[2] == b.axes[2] assert a.axes[3] == b.axes[3] assert a.sum() == b.sum() assert repr(a) == repr(b) assert str(a) == str(b) assert a == b def test_fill_bool_not_bool(): h = bh.Histogram(bh.axis.Boolean()) h.fill([0, 1, 1, 7, -3]) assert_array_equal(h.view(), [1, 4]) def test_pick_bool(): h = bh.Histogram(bh.axis.Boolean(), bh.axis.Boolean(metadata={"one": 1})) h.fill([True, True, False, False], [True, False, True, True]) h.fill([True, True, True], True) assert_array_equal(h[True, :].view(), [1, 4]) assert_array_equal(h[False, :].view(), [0, 2]) assert_array_equal(h[:, False].view(), [0, 1]) assert_array_equal(h[:, True].view(), [2, 4]) def test_slice_bool(): h = bh.Histogram(bh.axis.Boolean()) h.fill([0, 0, 0, 1, 3, 4, -2]) assert_array_equal(h.view(), [3, 4]) assert_array_equal(h[1:].view(), [4]) assert_array_equal(h[:1].view(), [3]) assert_array_equal(h[:1].axes[0].centers, [0.5]) assert_array_equal(h[1:].axes[0].centers, [1.5]) def test_pickle_bool(): a = bh.Histogram(bh.axis.Boolean(), bh.axis.Boolean(metadata={"one": 1})) assert isinstance(a, bh.Histogram) a.fill([True, True, False, False], [True, False, True, True]) a.fill([True, True, True], True) assert a[True, True] == 4 assert a[True, False] == 1 assert a[False, True] == 2 assert a[False, False] == 0 io = BytesIO() pickle.dump(a, io, protocol=-1) io.seek(0) b = pickle.load(io) assert id(a) != id(b) assert a.ndim == b.ndim assert a.axes[0] == b.axes[0] assert a.axes[1] == b.axes[1] assert a.sum() == b.sum() assert repr(a) == repr(b) assert str(a) == str(b) assert a == b assert_array_equal(a.view(), b.view()) # NumPy tests def test_numpy_conversion_0(): a = bh.Histogram(bh.axis.Integer(0, 3, underflow=False, overflow=False)) a.fill(0) for _ in range(5): a.fill(1) c = np.array(a) # a copy v = np.asarray(a) # a view for t in (c, v): assert t.dtype == np.double # CLASSIC: np.uint8 assert_array_equal(t, (1, 5, 0)) for _ in range(10): a.fill(2) # copy does not change, but view does assert_array_equal(c, (1, 5, 0)) assert_array_equal(v, (1, 5, 10)) for _ in range(255): a.fill(1) c = np.array(a) assert c.dtype == np.double # CLASSIC: np.uint16 assert_array_equal(c, (1, 260, 10)) # view does not follow underlying switch in word size # assert not np.all(c, v) def test_numpy_conversion_1(): # CLASSIC: was weight array h = bh.Histogram(bh.axis.Integer(0, 3)) for _ in range(10): h.fill(1, weight=3) c = np.array(h) # a copy v = np.asarray(h) # a view assert c.dtype == np.double # CLASSIC: np.float64 assert_array_equal(c, np.array((0, 30, 0))) assert_array_equal(v, c) def test_numpy_conversion_2(): a = bh.Histogram( bh.axis.Integer(0, 2, underflow=False, overflow=False), bh.axis.Integer(0, 3, underflow=False, overflow=False), bh.axis.Integer(0, 4, underflow=False, overflow=False), ) r = np.zeros((2, 3, 4), dtype=np.int8) for i in range(a.axes[0].extent): for j in range(a.axes[1].extent): for k in range(a.axes[2].extent): for _ in range(i + j + k): a.fill(i, j, k) r[i, j, k] = i + j + k d = np.zeros((2, 3, 4), dtype=np.int8) for i in range(a.axes[0].extent): for j in range(a.axes[1].extent): for k in range(a.axes[2].extent): d[i, j, k] = a[i, j, k] assert_array_equal(d, r) c = np.array(a) # a copy v = np.asarray(a) # a view assert_array_equal(c, r) assert_array_equal(v, r) def test_numpy_conversion_3(): a = bh.Histogram( bh.axis.Integer(0, 2), bh.axis.Integer(0, 3), bh.axis.Integer(0, 4), storage=bh.storage.Double(), ) r = np.zeros((4, 5, 6)) for i in range(a.axes[0].extent): for j in range(a.axes[1].extent): for k in range(a.axes[2].extent): a.fill(i - 1, j - 1, k - 1, weight=i + j + k) r[i, j, k] = i + j + k c = a.view(flow=True) assert_array_equal(c, r) assert a.sum() == approx(144) assert a.sum(flow=True) == approx(720) assert c.sum() == approx(720) def test_numpy_conversion_4(): a = bh.Histogram( bh.axis.Integer(0, 2, underflow=False, overflow=False), bh.axis.Integer(0, 4, underflow=False, overflow=False), ) a1 = np.asarray(a) assert a1.dtype == np.double assert a1.shape == (2, 4) b = bh.Histogram() b1 = np.asarray(b) assert b1.shape == () assert np.sum(b1) == 0 # Compare sum methods assert b.sum() == np.asarray(b).sum() def test_numpy_conversion_5(): a = bh.Histogram( bh.axis.Integer(0, 3, underflow=False, overflow=False), bh.axis.Integer(0, 2, underflow=False, overflow=False), storage=bh.storage.Unlimited(), ) a.fill(0, 0) for _ in range(80): a = a + a # a now holds a multiprecision type a.fill(1, 0) for _ in range(2): a.fill(2, 0) for _ in range(3): a.fill(0, 1) for _ in range(4): a.fill(1, 1) for _ in range(5): a.fill(2, 1) a1 = a.view() assert a1.shape == (3, 2) assert a1[0, 0] == float(2**80) assert a1[1, 0] == 1 assert a1[2, 0] == 2 assert a1[0, 1] == 3 assert a1[1, 1] == 4 assert a1[2, 1] == 5 @pytest.mark.parametrize( "dtype", [np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64], ) def test_fill_dtypes(dtype): a = bh.Histogram(bh.axis.Integer(0, 2), storage=bh.storage.Int64()) a.fill(np.array([0, 0, 0, 1, 1, 2], dtype=dtype)) a.fill(dtype(0)) assert list(a.values()) == [4, 2] def test_fill_with_sequence_0(): def fa(*args): return np.array(args, dtype=float) def ia(*args): return np.array(args, dtype=int) a = bh.Histogram(bh.axis.Integer(0, 2)) a.fill(np.array(1)) # 0-dim arrays work a.fill(ia(-1, 0, 1, 2)) a.fill((2, 1, 0, -1)) assert_array_equal(a.view(True), [2, 2, 3, 2]) with pytest.raises(ValueError): a.fill(np.empty((2, 2))) with pytest.raises(ValueError): a.fill(np.empty(2), 1) with pytest.raises(ValueError): a.fill(np.empty(2), np.empty(3)) with pytest.raises(TypeError): a.fill("abc") with pytest.raises(IndexError): a[1, 2] b = bh.Histogram(bh.axis.Regular(3, 0, 3)) b.fill(fa(0, 0, 1, 2)) b.fill(ia(1, 0, 2, 2)) assert_array_equal(b.view(True), [0, 3, 2, 3, 0]) c = bh.Histogram( bh.axis.Integer(0, 2, underflow=False, overflow=False), bh.axis.Regular(2, 0, 2) ) c.fill(ia(-1, 0, 1), fa(-1.0, 1.5, 0.5)) assert_array_equal(c.view(True), [[0, 0, 1, 0], [0, 1, 0, 0]]) # we don't support: assert a[[1, 1]].value, 0 with pytest.raises(ValueError): c.fill(1) with pytest.raises(ValueError): c.fill([1, 0, 2], [1, 1]) # this broadcasts c.fill([1, 0], -1) assert_array_equal(c.view(True), [[1, 0, 1, 0], [1, 1, 0, 0]]) c.fill([1, 0], 0) assert_array_equal(c.view(True), [[1, 1, 1, 0], [1, 2, 0, 0]]) c.fill(0, [-1, 0.5, 1.5, 2.5]) assert_array_equal(c.view(True), [[2, 2, 2, 1], [1, 2, 0, 0]]) with pytest.raises(IndexError): c[1] with pytest.raises(IndexError): c[1, 2, 3] @pytest.mark.xfail( platform.machine() == "ppc64le", reason="ppc64le bug (TBD)", strict=False ) def test_fill_with_sequence_1(): a = bh.Histogram(bh.axis.Integer(0, 3), storage=bh.storage.Weight()) v = np.array([-1, 0, 1, 2, 3, 4], dtype=int) w = np.array([2, 3, 4, 5, 6, 7], dtype=float) a.fill(v, weight=w) a.fill((0, 1), weight=(2, 3)) assert a[bh.underflow] == bh.accumulators.WeightedSum(2, 4) assert a[0] == bh.accumulators.WeightedSum(5, 13) assert a[1] == bh.accumulators.WeightedSum(7, 25) assert a[2] == bh.accumulators.WeightedSum(5, 25) assert a[bh.underflow].value == 2 assert a[0].value == 5 assert a[1].value == 7 assert a[2].value == 5 assert a[bh.underflow].variance == 4 assert a[0].variance == 13 assert a[1].variance == 25 assert a[2].variance == 25 a.fill((1, 2), weight=1) a.fill(0, weight=1) a.fill(0, weight=2) assert a[0].value == 8 assert a[1].value == 8 assert a[2].value == 6 with pytest.raises(TypeError): a.fill((1, 2), foo=(1, 1)) with pytest.raises(ValueError): a.fill((1, 2, 3), weight=(1, 2)) with pytest.raises(ValueError): a.fill((1, 2), weight="ab") with pytest.raises(TypeError): a.fill((1, 2), weight=(1, 1), foo=1) with pytest.raises(ValueError): a.fill((1, 2), weight=([1, 1], [2, 2])) a = bh.Histogram(bh.axis.Integer(0, 3)) # this broadcasts a.fill((1, 2), weight=1) assert a[1] == 1.0 assert a[2] == 1.0 a = bh.Histogram( bh.axis.Integer(0, 2, underflow=False, overflow=False), bh.axis.Regular(2, 0, 2, underflow=False, overflow=False), ) a.fill((-1, 0, 1), (-1, 1, 0.1)) assert a[0, 0] == 0 assert a[0, 1] == 1 assert a[1, 0] == 1 assert a[1, 1] == 0 a = bh.Histogram(bh.axis.Integer(0, 3, underflow=False, overflow=False)) a.fill((0, 0, 1, 2)) a.fill((1, 0, 2, 2)) assert a[0] == 3 assert a[1] == 2 assert a[2] == 3 def test_fill_with_sequence_2(): a = bh.Histogram(bh.axis.StrCategory(["A", "B"])) a.fill("A") a.fill(np.array("B")) # 0-dim array is also accepted a.fill(("A", "B", "C")) assert_array_equal(a.view(True), [2, 2, 1]) a.fill(np.array(("D", "B", "A"), dtype="S5")) a.fill(np.array(("D", "B", "A"), dtype="U1")) assert_array_equal(a.view(True), [4, 4, 3]) with pytest.raises(ValueError): a.fill(np.array((("B", "A"), ("C", "A")))) # ndim == 2 not allowed b = bh.Histogram( bh.axis.Integer(0, 2, underflow=False, overflow=False), bh.axis.StrCategory(["A", "B"]), ) b.fill((1, 0, 10), ("C", "B", "A")) assert_array_equal(b.view(True), [[0, 1, 0], [0, 0, 1]]) def test_fill_with_sequence_3(): h = bh.Histogram(bh.axis.StrCategory([], growth=True)) h.fill("A") assert h.axes[0].size == 1 h.fill(["A"]) assert h.axes[0].size == 1 h.fill(["A", "B"]) assert h.axes[0].size == 2 assert_array_equal(h.view(True), [3, 1]) def test_fill_with_sequence_4(): h = bh.Histogram( bh.axis.StrCategory([], growth=True), bh.axis.Integer(0, 0, growth=True) ) h.fill("1", np.arange(2)) assert h.axes[0].size == 1 assert h.axes[1].size == 2 assert_array_equal(h.view(True), [[1, 1]]) with pytest.raises(ValueError): h.fill(["1"], np.arange(2)) # lengths do not match def test_axes_reference(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(20, 2, 4, metadata=12), bh.axis.StrCategory([], growth=True), ) h.axes[0].metadata = "set1" h.axes[1].metadata = None h_copy = h[...] assert h_copy.axes[0].metadata == "set1" assert h_copy.axes[1].metadata is None assert h_copy.axes[2].size == 0 h_copy.fill([0.3], [3.2], ["check"]) assert h_copy.axes[2].size == 1 def test_axes_lifetime(): h = bh.Histogram(bh.axis.Regular(10, 0, 1, metadata=2)) ax = h.axes[0] if platform.python_implementation == "CPython": # 2 is the minimum refcount, so the *python* object should be deleted # after the del; hopefully the C++ object lives through the axis instance. assert sys.getrefcount(h) == 2 del h assert ax.metadata == 2 ax.metadata = 3 assert ax.metadata == 3 def test_copy_axes(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h2 = h.copy() h.axes[0].metadata = 1 assert h2.axes[0].metadata is None def test_shape(): h = bh.Histogram( bh.axis.Regular(7, 0, 1), bh.axis.Regular(13, 0, 1), bh.axis.Regular(17, 0, 1), bh.axis.Regular(24, 0, 1), ) assert h.shape == (7, 13, 17, 24) def test_empty_shape(): h = bh.Histogram() assert h.shape == () # issue #416 a def test_hist_division(): edges = [0, 0.25, 0.5, 0.75, 1, 2, 3, 4, 7, 10] edges = [-x for x in reversed(edges)] + edges[1:] h = bh.Histogram(bh.axis.Variable(edges)) h[...] = 1 h1 = h.copy() dens = h.view().copy() dens /= h.axes[0].widths * h.sum() h1 /= h.axes[0].widths * h.sum() assert_array_equal(h1.view(), dens) # issue #416 b # def test_hist_division(): # edges = [0, .25, .5, .75, 1, 2, 3, 4, 7, 10] # edges = [-x for x in reversed(edges)] + edges[1:] # # h = bh.Histogram(bh.axis.Variable(edges)) # h[...] = 1 # # dens = h.view().copy() / h.axes[0].widths * h.sum() # h1 = h.copy() # # h1[:] /= h.axes[0].widths * h.sum() # # assert_allclose(h1.view(), dens) def test_add_hists(): edges = [0, 0.25, 0.5, 0.75, 1, 2, 3, 4, 7, 10] edges = [-x for x in reversed(edges)] + edges[1:] h = bh.Histogram(bh.axis.Variable(edges)) h[...] = 1 h1 = h.copy() h1 += h.view() h2 = h.copy() h2 += h1 h3 = h.copy() h3 += 5 assert_array_equal(h, 1) assert_array_equal(h1, 2) assert_array_equal(h2, 3) assert_array_equal(h3, 6) def test_add_broadcast(): h = bh.Histogram(bh.axis.Regular(10, 0, 1), bh.axis.Regular(20, 0, 1)) h1 = h.copy() h2 = h.copy() h1[...] = 1 assert h1.view().sum() == 10 * 20 assert h1.view(flow=True).sum() == 10 * 20 h2 = h + [[1]] # noqa: RUF005 assert h2.sum() == 10 * 20 assert h2.sum(flow=True) == 12 * 22 h3 = h + np.ones((10, 20)) assert h3.sum() == 10 * 20 assert h3.sum(flow=True) == 10 * 20 h4 = h + np.ones((12, 22)) assert h4.view(flow=True).sum() == 12 * 22 h5 = h + np.ones((10, 1)) assert h5.sum(flow=True) == 10 * 20 h5 = h + np.ones((1, 22)) assert h5.sum(flow=True) == 12 * 22 # Issue #431 def test_mul_shallow(): my_lock = threading.Lock() h = bh.Histogram(bh.axis.Integer(0, 3, metadata=my_lock), metadata=my_lock) h.fill([0, 0, 0, 1]) h2 = h * 2 assert h.metadata is h2.metadata assert h.axes[0].metadata is h2.axes[0].metadata def test_reductions(): h = bh.Histogram(bh.axis.Variable([1, 2, 4, 7, 9, 9.5, 10])) widths_1 = functools.reduce(operator.mul, h.axes.widths) widths_2 = np.prod(h.axes.widths, axis=0) assert_array_equal(widths_1, widths_2) # Issue 435 def test_np_scalars(): hist = bh.Histogram(bh.axis.Regular(30, 1, 500, transform=bh.axis.transform.log)) hist.fill([7, 7]) hist2 = hist / np.float64(2.0) assert hist2[bh.loc(7)] == 1.0 hist2 = hist / hist.axes.widths.prod(axis=0) assert hist2[bh.loc(7)] == approx(1.3467513416439476) with pytest.raises(ValueError): hist / np.array([1, 2, 3]) hist /= np.float64(2.0) assert hist[bh.loc(7)] == 1.0 def test_sum_empty_axis(): hist = bh.Histogram( bh.axis.StrCategory("", growth=True), bh.axis.Regular(10, 0, 1), storage=bh.storage.Weight(), ) assert hist.sum().value == 0 assert "Str" in repr(hist) # Issue #618 def test_negative_fill(count_storage): h = bh.Histogram(bh.axis.Integer(0, 3), storage=count_storage()) h.fill(1, weight=-1) answer = np.array([0, -1, 0]) assert h.values() == approx(answer) # Issue #589 def test_underfill_growth(): h = bh.Histogram(bh.axis.Regular(10, 0, 1, growth=True)) h.fill(2) h.fill(-1) assert h.sum() == 2 scikit-hep-boost-histogram-5f6f3e3/tests/test_histogram_indexing.py000066400000000000000000000423611516627434300257720ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest from numpy.testing import assert_array_equal from pytest import approx import boost_histogram as bh def test_1D_get_bin(): h = bh.Histogram(bh.axis.Regular(10, 0, 0.99)) h.fill([0.25, 0.25, 0.25, 0.15]) assert h[0] == 0 assert h[1] == 1 assert h[2] == 3 assert h[bh.loc(0)] == 0 assert h[bh.loc(0.1)] == 1 assert h[bh.loc(0.1) + 1] == 3 assert h[bh.loc(0.2)] == 3 assert h.view()[2] == h[2] with pytest.raises(IndexError): h[1, 2] def test_2D_get_bin(): h = bh.Histogram(bh.axis.Regular(10, 0, 0.99), bh.axis.Regular(10, 0, 0.99)) h.fill(0.15, [0.25, 0.25, 0.25, 0.15]) assert h[0, 0] == 0 assert h[0, 1] == 0 assert h[1, 1] == 1 assert h[1, 2] == 3 assert h[bh.loc(0.1), bh.loc(0.2)] == 3 assert h[bh.loc(0) + 1, bh.loc(0.3) - 1] == 3 assert h.view()[1, 2] == h[1, 2] with pytest.raises(IndexError): h[1] def test_get_1D_histogram(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h.fill([0.25, 0.25, 0.25, 0.15]) h2 = h[:] assert h == h2 h.fill(0.75) assert h != h2 def test_get_1D_slice(): h1 = bh.Histogram(bh.axis.Regular(10, 0, 1)) h2 = bh.Histogram(bh.axis.Regular(5, 0, 0.5)) h1.metadata = {"that": 3} h1.fill([0.25, 0.25, 0.25, 0.15]) h2.fill([0.25, 0.25, 0.25, 0.15]) assert h1 != h2 assert h1[:5] == h2 assert h1[: bh.loc(0.5)] == h2 assert h1[2:4] == h2[2:4] assert h1[bh.loc(0.2) : bh.loc(0.4)] == h2[bh.loc(0.2) : bh.loc(0.4)] assert len(h1[2:4].view()) == 2 assert len(h1[2 : 4 : bh.rebin(2)].view()) == 1 assert len(h1[:: bh.rebin(2)].view()) == 5 # Shortcut assert len(h1[bh.rebin(2)].view()) == 5 assert h1[2:4].metadata == {"that": 3} def test_ellipsis(): h = bh.Histogram(bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1)) assert h == h[...] assert h == h[:, ...] assert h == h[..., :] assert h == h[:, :, ...] assert h == h[:, ..., :] assert h == h[..., :, :] with pytest.raises(IndexError): h[:, :, :, ...] with pytest.raises(IndexError): h[:, :, ..., :] with pytest.raises(IndexError): h[..., :, :, :] with pytest.raises(IndexError): h[..., ...] assert h[2:4, ...] == h[2:4, :] def test_basic_projection(): h2 = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), ) h1 = bh.Histogram(bh.axis.Regular(10, 0, 10)) contents = [[2, 2, 2, 3, 4, 5, 6], [1, 2, 2, 3, 2, 1, 2], [-12, 33, 4, 9, 2, 4, 9]] h1.fill(contents[0]) h2.fill(*contents) assert h1 == h2[:, :: bh.sum, :: bh.sum] assert h1 == h2[..., :: bh.sum, :: bh.sum] assert h2.sum(flow=True) == h2[:: bh.sum, :: bh.sum, :: bh.sum] # Python's builtin sum is identical to bh.sum assert bh.sum is sum assert h1 == h2[:, ::sum, ::sum] assert h1 == h2[..., ::sum, ::sum] assert h2.sum(flow=True) == h2[::sum, ::sum, ::sum] # Shortcut assert h1 == h2[:, sum, sum] assert h1 == h2[..., sum, sum] assert h2.sum(flow=True) == h2[sum, sum, sum] def test_slicing_projection(): h1 = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), ) X, Y, Z = np.mgrid[-0.5:10.5:12j, -0.5:10.5:12j, -0.5:10.5:12j] h1.fill(X.ravel(), Y.ravel(), Z.ravel()) assert h1[:: bh.sum, :: bh.sum, :: bh.sum] == 12**3 assert h1[0 : len : bh.sum, 0 : len : bh.sum, 0 : len : bh.sum] == 10**3 assert h1[0 : bh.overflow : bh.sum, 0 : len : bh.sum, :: bh.sum] == 10 * 10 * 12 assert h1[:: bh.sum, 0 : len : bh.sum, :: bh.sum] == 10 * 12 * 12 # make sure nothing was modified assert h1.sum() == 10**3 assert h1.sum(flow=True) == 12**3 h2 = h1[0 : 3 : bh.sum, ...] assert h2[1, 2] == 3 h3 = h2[:, 5 : 7 : bh.sum] assert h3[1] == 6 # Select one bin assert h1[2, :: bh.sum, :: bh.sum] == 12 * 12 # Select one bin assert h1[2, 7, :: bh.sum] == 12 def test_mix_value_with_slice(): h = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), bh.axis.Integer(0, 2) ) vals = np.arange(100).reshape(10, 10, 1) h[:, :, 1:2] = vals print(h.view()[:3, :3, :]) assert h[0, 1, True] == 1 assert h[1, 0, True] == 10 assert h[1, 1, True] == 11 assert h[3, 4, False] == 0 assert_array_equal(h[:, :, True].view(), vals[:, :, 0]) assert_array_equal(h[:, :, False].view(), 0) def test_mix_value_with_slice_2(): h = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10), bh.axis.Integer(0, 2) ) vals = np.arange(100).reshape(10, 10) h[:, :, True] = vals assert h[0, 1, True] == 1 assert h[1, 0, True] == 10 assert h[1, 1, True] == 11 assert h[3, 4, False] == 0 assert_array_equal(h[:, :, True].view(), vals) assert_array_equal(h[:, :, False].view(), 0) h2 = h[bh.rebin(2), bh.rebin(5), :] assert_array_equal(h2.shape, (5, 2, 2)) def test_one_sided_slice(): h = bh.Histogram(bh.axis.Regular(4, 1, 5)) h.view(True)[:] = 1 assert h[sum] == 6 # 4 (internal bins) + 2 (flow bins) assert h[bh.tag.at(-1) : bh.tag.at(5) : sum] == 6 # keeps underflow, keeps overflow # check that slicing without bh.sum adds removed counts to flow bins assert_array_equal(h[1:3].view(True), [2, 1, 1, 2]) assert h[0::sum] == 5 # removes underflow, keeps overflow assert h[:4:sum] == 5 # removes overflow, keeps underflow assert h[0:4:sum] == 4 # removes underflow and overflow assert h[bh.loc(1) :: sum] == 5 # remove underflow assert h[: bh.loc(5) : sum] == 5 # remove overflow assert h[bh.loc(1) : bh.loc(5) : sum] == 4 # removes underflow and overflow assert h[bh.loc(0) :: sum] == 6 # keep underflow assert h[: bh.loc(10) + 1 : sum] == 6 # keep overflow assert h[bh.loc(0) : bh.loc(10) + 1 : sum] == 6 def test_repr(): assert repr(bh.loc(2)) == "loc(2)" assert repr(bh.loc(3) + 1) == "loc(3) + 1" assert repr(bh.loc(1) - 2) == "loc(1) - 2" assert repr(bh.underflow) == "underflow" assert repr(bh.underflow + 1) == "underflow + 1" assert repr(bh.underflow - 1) == "underflow - 1" assert repr(bh.overflow) == "overflow" assert repr(bh.overflow + 1) == "overflow + 1" assert repr(bh.overflow - 1) == "overflow - 1" assert repr(bh.rebin(2)) == "rebin(factor=2)" # Was broken in 0.6.1 def test_noflow_slicing(): noflow = {"underflow": False, "overflow": False} h = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10, **noflow), bh.axis.Integer(0, 2, **noflow), ) vals = np.arange(100).reshape(10, 10) h[:, :, True] = vals assert h[0, 1, True] == 1 assert h[1, 0, True] == 10 assert h[1, 1, True] == 11 assert h[3, 4, False] == 0 assert h[{0: 3, 1: 4, 2: False}] == 0 assert_array_equal(h[:, :, True].view(), vals) assert_array_equal(h[:, :, False].view(), 0) def test_singleflow_slicing(): h = bh.Histogram( bh.axis.Integer(0, 4, underflow=False), bh.axis.Integer(0, 4, overflow=False) ) vals = np.arange(4 * 4).reshape(4, 4) h[:, :] = vals assert h[0, 0] == 0 assert h[0, 1] == 1 assert h[1, 0] == 4 assert h[1, 1] == 5 assert_array_equal(h[:, 1 : 3 : bh.sum], vals[:, 1:3].sum(axis=1)) assert_array_equal(h[{1: slice(1, 3, bh.sum)}], vals[:, 1:3].sum(axis=1)) assert_array_equal(h[1 : 3 : bh.sum, :], vals[1:3, :].sum(axis=0)) def test_set_range_with_scalar(): h = bh.Histogram(bh.axis.Integer(0, 10)) h[2:5] = 42 assert h[1] == 0 assert h[2] == 42 assert h[3] == 42 assert h[4] == 42 assert h[5] == 0 def test_set_range_with_scalar_callable(): h = bh.Histogram(bh.axis.Integer(0, 10)) h[2:len] = 42 assert h[1] == 0 assert h[2] == 42 assert h[3] == 42 assert h[4] == 42 assert h[5] == 42 assert h[bh.overflow] == 0 def test_set_all_with_scalar(): h = bh.Histogram(bh.axis.Integer(0, 10)) h[:] = 42 assert h[0] == 42 assert h[9] == 42 assert h[::sum] == 42 * 10 def test_pick_str_category(): noflow = {"underflow": False, "overflow": False} h = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10, **noflow), bh.axis.StrCategory(["on", "off", "maybe"]), ) vals = np.arange(100).reshape(10, 10) h[:, :, bh.loc("on")] = vals assert h[0, 1, bh.loc("on")] == 1 assert h[1, 0, bh.loc("on")] == 10 assert h[1, 1, bh.loc("on")] == 11 assert h[3, 4, bh.loc("maybe")] == 0 assert_array_equal(h[:, :, bh.loc("on")].view(), vals) assert_array_equal(h[{2: bh.loc("on")}].view(), vals) assert_array_equal(h[:, :, bh.loc("off")].view(), 0) def test_string_requirement(): h = bh.Histogram( bh.axis.Integer(0, 10), bh.axis.StrCategory(["1", "a", "hello"]), storage=bh.storage.Int64(), ) with pytest.raises(TypeError): h[bh.loc("1"), bh.loc(1)] with pytest.raises(TypeError): h[bh.loc(1), bh.loc(1)] with pytest.raises(TypeError): h[bh.loc("1"), bh.loc("1")] assert h[bh.loc(1), bh.loc("1")] == 0 def test_pick_int_category(): noflow = {"underflow": False, "overflow": False} h = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.Regular(10, 0, 10, **noflow), bh.axis.IntCategory([3, 5, 7, 12, 13]), ) vals = np.arange(100).reshape(10, 10) h[:, :, bh.loc(3)] = vals h[:, :, bh.loc(5)] = vals + 1 h[:, :, 3] = vals + 100 assert h[0, 1, bh.loc(3)] == 1 assert h[1, 0, bh.loc(5)] == 10 + 1 assert h[1, 1, bh.loc(5)] == 11 + 1 assert h[3, 4, bh.loc(7)] == 0 assert h[3, 4, bh.loc(12)] == 134 assert_array_equal(h[:, :, bh.loc(3)].view(), vals) assert_array_equal(h[{2: bh.loc(3)}].view(), vals) assert_array_equal(h[:, :, bh.loc(5)].view(), vals + 1) assert_array_equal(h[:, :, bh.loc(7)].view(), 0) @pytest.mark.parametrize( "ax", [bh.axis.Regular(3, 0, 1), bh.axis.Variable([0, 0.3, 0.6, 1])], ids=["regular", "variable"], ) def test_pick_flowbin(ax): w = 1e-2 # e.g. a cross section for a process x = [-0.1, -0.1, 0.1, 0.1, 0.1] y = [-0.1, 0.1, -0.1, -0.1, 0.1] h = bh.Histogram( ax, ax, storage=bh.storage.Weight(), ) h.fill(x, y, weight=w) uf_slice = h[bh.tag.underflow, ...] assert uf_slice.values(flow=True) == approx(np.array([1, 1, 0, 0, 0]) * w) uf_slice = h[..., bh.tag.underflow] assert uf_slice.values(flow=True) == approx(np.array([1, 2, 0, 0, 0]) * w) def test_axes_tuple(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) assert isinstance(h.axes[:1], bh.axis.AxesTuple) assert isinstance(h.axes[0], bh.axis.Regular) (before,) = h.axes.centers[:1] (after,) = h.axes[:1].centers assert_array_equal(before, after) def test_axes_tuple_Nd(): h = bh.Histogram( bh.axis.Integer(0, 5), bh.axis.Integer(0, 4), bh.axis.Integer(0, 6) ) assert isinstance(h.axes[:2], bh.axis.AxesTuple) assert isinstance(h.axes[1], bh.axis.Integer) b1, b2 = h.axes.centers[1:3] a1, a2 = h.axes[1:3].centers assert_array_equal(b1.flatten(), a1.flatten()) assert_array_equal(b2.flatten(), a2.flatten()) assert b1.ndim == 3 assert a1.ndim == 2 # issue 556 def test_single_flow_bin(): # Flow is removed for category axes unless full sum is used h = bh.Histogram(bh.axis.IntCategory([0, 1, 2])) h.view(True)[:] = 1 assert h[::sum] == 4 assert h[0::sum] == 3 assert h[1::sum] == 2 assert h[2::sum] == 1 with pytest.raises(ValueError): h[3::sum] assert h[1:2][sum] == 4 h = bh.Histogram(bh.axis.Integer(0, 3)) h.view(True)[:] = 1 assert h[::sum] == 5 assert h[0::sum] == 4 assert h[1::sum] == 3 assert h[2::sum] == 2 assert h[3::sum] == 1 assert h[1:2][sum] == 5 # issue 579 def test_scale_flowbins(): w = 1e-1 x = np.random.normal(loc=0.4, scale=0.4, size=100) h = bh.Histogram(bh.axis.Variable([0, 0.5, 1]), storage=bh.storage.Weight()) h.fill(x, weight=w) ref_value = h.values(flow=True) * 5 scale_value = (h * 5).values(flow=True) assert scale_value == approx(ref_value) def test_add_flowbins(): h = bh.Histogram(bh.axis.Variable([0, 0.5, 1]), storage=bh.storage.Weight()) ref_value = h.values(flow=True) + 5 scale_value = (h + 5).values(flow=True) assert scale_value == approx(ref_value) # issue 737 def test_large_index(): h = bh.Histogram( bh.axis.IntCategory([4, 8, 15, 16, 23, 42, 99_999_001, 1_000_010_020]) ) assert h.axes[0].value(6) == 99_999_001 assert h.axes[0].index(99_999_001) == 6 def test_scaling_slice(): h = bh.Histogram(bh.axis.Regular(3, 0, 3), bh.axis.StrCategory(["a", "b"])) h.fill([1, 1, 2], "a") h.fill([0], "b") h[:, bh.loc("a")] *= 2 assert h[1, 0] == approx(4) assert h[2, 0] == approx(2) assert h[0, 1] == approx(1) def test_scaling_slice_weight(): h = bh.Histogram( bh.axis.Regular(3, 0, 3), bh.axis.StrCategory(["a", "b"]), storage=bh.storage.Weight(), ) h.fill([1, 1, 2], "a") h.fill([0], "b") h[:, bh.loc("a")] *= 2 assert h[1, 0].value == approx(4) assert h[2, 0].value == approx(2) assert h[0, 1].value == approx(1) def test_setting_histogram_mismatch(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), bh.axis.Integer(0, 20)) h[:, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10)) h[0:, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10, underflow=False)) h[:len, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10, overflow=False)) h[0:len, 0] = bh.Histogram( bh.axis.Regular(10, 0, 10, underflow=False, overflow=False) ) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[0:, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10)) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[:len, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10)) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[:, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10, underflow=False)) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[:, 0] = bh.Histogram(bh.axis.Regular(10, 0, 10, overflow=False)) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[:, 0] = bh.Histogram( bh.axis.Regular(10, 0, 10, underflow=False, overflow=False) ) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[0:, 0] = bh.Histogram( bh.axis.Regular(10, 0, 10, underflow=False, overflow=False) ) with pytest.raises(ValueError, match="Cannot set histogram with underflow"): h[:len, 0] = bh.Histogram( bh.axis.Regular(10, 0, 10, underflow=False, overflow=False) ) def test_setting_histogram_slice_not_at_start(): """Regression test: __setitem__ with slices that are not at the start of the index. Previously, h[2,:,:,2] = other_hist would use the wrong axis indices when checking underflow/overflow traits of the value histogram, causing either a ValueError (wrong axis compared) or IndexError (index out of range). """ axis_wUnderflow0 = bh.axis.Regular(10, 0, 1, underflow=True, overflow=True) axis_wUnderflow1 = bh.axis.Regular(5, 0, 5, underflow=True, overflow=True) axis_wUnderflow2 = bh.axis.Regular(7, 0, 3, underflow=True, overflow=True) axis_noUnderflow = bh.axis.Regular(8, -2, 2, underflow=False, overflow=True) h_2D = bh.Histogram(axis_noUnderflow, axis_wUnderflow0) h_2D.fill([-1.1, -0.2, 0.3, 1.4], [0.2, 0.5, 0.3, 0.8]) # Slices in the middle of the index h_middle = bh.Histogram( axis_wUnderflow1, axis_noUnderflow, axis_wUnderflow0, axis_wUnderflow2 ) h_middle[2, :, :, 2] = h_2D # previously raised ValueError # Slices at the end of the index h_last = bh.Histogram( axis_wUnderflow1, axis_wUnderflow2, axis_noUnderflow, axis_wUnderflow0 ) h_last[2, 2, :, :] = h_2D # previously raised IndexError # Slices at the start (sanity check - this always worked) h_first = bh.Histogram( axis_noUnderflow, axis_wUnderflow0, axis_wUnderflow1, axis_wUnderflow2 ) h_first[:, :, 2, 2] = h_2D # Verify that the data was correctly set (use a non-flow slice comparison) np.testing.assert_array_equal(h_first.view()[:, :, 2, 2], h_2D.view()) np.testing.assert_array_equal(h_middle.view()[2, :, :, 2], h_2D.view()) np.testing.assert_array_equal(h_last.view()[2, 2, :, :], h_2D.view()) def test_rebin_groups_no_inplace_modification(): """ Test that rebinning with a groups list does not mutate the input list in-place. This ensures consecutive rebin operations with the same list do not fail. """ h1 = bh.Histogram(bh.axis.Regular(60, 0, 600)) h2 = bh.Histogram(bh.axis.Regular(60, 0, 600)) rebinner = [5] * 12 h3 = h1[bh.rebin(groups=rebinner)] # The original list should remain unchanged assert rebinner == [5] * 12, "rebinner list was mutated in-place" # Second rebin should not raise h4 = h2[bh.rebin(groups=rebinner)] assert h3 == h4 scikit-hep-boost-histogram-5f6f3e3/tests/test_histogram_init.py000066400000000000000000000076531516627434300251350ustar00rootroot00000000000000from __future__ import annotations import pytest from pytest import approx import boost_histogram as bh @pytest.mark.parametrize(("opt", "extent"), [("uo", 2), ("", 0)]) def test_make_regular_1D(opt, extent): hist = bh.Histogram( bh.axis.Regular(3, 2, 5, underflow="u" in opt, overflow="o" in opt) ) assert hist.ndim == 1 assert hist.axes[0].size == 3 assert hist.axes[0].extent == 3 + extent assert hist.axes[0].bin(1) == (3, 4) @pytest.mark.filterwarnings("ignore") def test_shortcuts(): hist = bh.Histogram((1, 2, 3), (10, 0, 1)) assert hist.ndim == 2 for i in range(2): assert isinstance(hist.axes[i], bh.axis.Regular) assert not isinstance(hist.axes[i], bh.axis.Variable) @pytest.mark.filterwarnings("ignore") def test_shortcuts_with_metadata(): with pytest.raises(TypeError): bh.Histogram((1, 2, 3, 4)) with pytest.raises(TypeError): bh.Histogram((1, 2)) with pytest.raises(TypeError): bh.Histogram((1, 2, 3, 4, 5)) with pytest.raises(TypeError): bh.Histogram((1, 2, 3, "this")) @pytest.mark.parametrize(("opt", "extent"), [("uo", 2), ("", 0)]) def test_make_regular_2D(opt, extent): hist = bh.Histogram( bh.axis.Regular(3, 2, 5, underflow="u" in opt, overflow="o" in opt), bh.axis.Regular(5, 1, 6, underflow="u" in opt, overflow="o" in opt), ) assert hist.ndim == 2 assert hist.axes[0].size == 3 assert hist.axes[0].extent == 3 + extent assert hist.axes[0].bin(1) == approx((3, 4)) assert hist.axes[1].size == 5 assert hist.axes[1].extent == 5 + extent assert hist.axes[1].bin(1) == approx((2, 3)) @pytest.mark.parametrize( "storage", [ bh.storage.Int64(), bh.storage.Double(), bh.storage.Unlimited(), bh.storage.Weight(), ], ) def test_make_any_hist(storage): hist = bh.Histogram( bh.axis.Regular(3, 1, 4), bh.axis.Regular(2, 2, 4, underflow=False, overflow=False), bh.axis.Regular(4, 1, 5, circular=True), storage=storage, ) assert hist.ndim == 3 assert hist.axes[0].size == 3 assert hist.axes[0].extent == 5 assert hist.axes[0].bin(1) == approx((2, 3)) assert hist.axes[1].size == 2 assert hist.axes[1].extent == 2 assert hist.axes[1].bin(1) == approx((3, 4)) assert hist.axes[2].size == 4 assert hist.axes[2].extent == 5 assert hist.axes[2].bin(1) == approx((2, 3)) def test_make_any_hist_storage(): assert not isinstance( bh.Histogram(bh.axis.Regular(5, 1, 2), storage=bh.storage.Int64())[0], float ) assert isinstance( bh.Histogram(bh.axis.Regular(5, 1, 2), storage=bh.storage.Double())[0], float ) assert isinstance(bh.Histogram(bh.axis.Regular(5, 1, 2), storage=None)[0], float) def test_issue_axis_bin_swan(): hist = bh.Histogram( bh.axis.Regular(10, 0, 10, metadata="x", transform=bh.axis.transform.sqrt), bh.axis.Regular(10, 0, 1, metadata="y", circular=True), ) b = hist.axes[1].bin(1) assert repr(b) == "(0.1, 0.2)" assert b[0] == approx(0.1) assert b[1] == approx(0.2) assert hist.axes[0].bin(0)[0] == 0 assert hist.axes[0].bin(1)[0] == approx(0.1) assert hist.axes[0].bin(2)[0] == approx(0.4) hist_ax = ( bh.axis.Regular(5, 1, 2, metadata=None), bh.axis.Regular(5, 1, 2, metadata=None, overflow=False, underflow=False), bh.axis.Integer(0, 5, metadata=None), ) hist_storage = ( bh.storage.Double, bh.storage.Unlimited, bh.storage.Int64, bh.storage.AtomicInt64, bh.storage.Weight, ) @pytest.mark.parametrize("ax", hist_ax) @pytest.mark.parametrize("storage", hist_storage) def test_make_selection(ax, storage): histogram = bh.Histogram(ax, storage=storage()) assert isinstance(histogram, bh.Histogram) histogram = bh.Histogram(ax, ax, storage=storage()) assert isinstance(histogram, bh.Histogram) # TODO: Make this test do something useful scikit-hep-boost-histogram-5f6f3e3/tests/test_histogram_set.py000066400000000000000000000070701516627434300247560ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest from numpy.testing import assert_array_equal import boost_histogram as bh def test_1D_set_bin(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h[2] = 2 assert h[2] == 2.0 h[bh.underflow] = 3 assert h[bh.underflow] == 3.0 h[bh.overflow] = 4 assert h[bh.overflow] == 4.0 def test_2d_set_bin(): h = bh.Histogram(bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1)) h[2, 4] = 2 assert h[2, 4] == 2.0 h[bh.underflow, 5] = 3 assert h[bh.underflow, 5] == 3.0 h[bh.overflow, bh.overflow] = 4 assert h[bh.overflow, bh.overflow] == 4.0 def test_1d_set_array(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h[...] = np.arange(10) assert_array_equal(h.view(), np.arange(10)) h[...] = np.arange(12) assert_array_equal(h.view(flow=True), np.arange(12)) with pytest.raises(ValueError): h[...] = np.arange(9) with pytest.raises(ValueError): h[...] = np.arange(11) with pytest.raises(ValueError): h[...] = np.arange(13) h[...] = 1 assert_array_equal(h.view(), np.ones(10)) def test_2d_set_array(): h = bh.Histogram(bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1)) h[...] = np.arange(10).reshape(-1, 1) assert_array_equal(h.view()[:, 2], np.arange(10)) h[...] = np.arange(12).reshape(-1, 1) assert_array_equal(h.view(flow=True)[:, 3], np.arange(12)) with pytest.raises(ValueError): h[...] = np.arange(9).reshape(-1, 1) with pytest.raises(ValueError): h[...] = np.arange(11).reshape(-1, 1) with pytest.raises(ValueError): h[...] = np.arange(13).reshape(-1, 1) h[...] = 1 assert_array_equal(h.view(), np.ones((10, 10))) def test_weighted_set_shortcut(): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=bh.storage.Weight()) h[0] = (1, 2) assert h[0].value == 1 assert h[0].variance == 2 h[3:6] = ((3, 4), (5, 6), (7, 8)) assert h[3].value == 3 assert h[3].variance == 4 assert h[4].value == 5 assert h[4].variance == 6 assert h[5].value == 7 assert h[5].variance == 8 @pytest.mark.parametrize( ("storage", "default"), [ (bh.storage.Mean, bh.accumulators.Mean(1.0, 2.0, 3.0)), (bh.storage.WeightedMean, bh.accumulators.WeightedMean(1.0, 2.0, 3.0, 4.0)), (bh.storage.Weight, bh.accumulators.WeightedSum(1.0, 2)), ], ) def test_set_special_dtype(storage, default): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(10, 0, 1), storage=storage() ) arr = np.full((10, 1), default) h[...] = arr assert_array_equal(h.view()[:, 1:2], arr) arr = np.full((12, 1), default) h[...] = arr assert_array_equal(h.view(flow=True)[:, 2:3], arr) arr = np.full((10, 10), default) h[...] = arr assert_array_equal(h.view(), arr) arr = np.full((10, 12), default) h[...] = arr assert_array_equal(h.view(flow=True)[1:11, :], arr) arr = np.full((12, 10), default) h[...] = arr assert_array_equal(h.view(flow=True)[:, 1:11], arr) arr = np.full((12, 12), default) h[...] = arr assert_array_equal(h.view(flow=True), arr) arr = np.full((9, 1), default) with pytest.raises(ValueError): h[...] = arr arr = np.full((11, 1), default) with pytest.raises(ValueError): h[...] = arr arr = np.full((13, 1), default) with pytest.raises(ValueError): h[...] = arr with pytest.raises(ValueError): h[...] = 1 with pytest.raises(ValueError): h[1, 1] = 1 scikit-hep-boost-histogram-5f6f3e3/tests/test_internal_histogram.py000066400000000000000000000253571516627434300260070ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest from numpy.testing import assert_allclose, assert_array_equal from pytest import approx import boost_histogram as bh methods = (bh.storage.Double, bh.storage.Int64, bh.storage.Unlimited) @pytest.mark.parametrize("dtype", [np.double, np.int_, np.float32]) def test_noncontig_fill(dtype): a = np.array([[0, 0], [1, 1]], dtype=dtype, order="C") b = np.array([[0, 0], [1, 1]], dtype=dtype, order="F") h1 = bh.Histogram(bh.axis.Regular(10, 0, 2)).fill(a[0]) h2 = bh.Histogram(bh.axis.Regular(10, 0, 2)).fill(b[0]) assert h1 == h2 @pytest.mark.parametrize("storage", methods) def test_1D_fill_int(storage): bins = 10 ranges = (0, 1) vals = (0.15, 0.25, 0.25) hist = bh.Histogram(bh.axis.Regular(bins, *ranges), storage=storage()) assert hist.storage_type == storage hist.fill(vals) H = np.array([0, 1, 2, 0, 0, 0, 0, 0, 0, 0]) assert_array_equal(np.asarray(hist), H) assert_array_equal(hist.view(flow=False), H) assert_array_equal(hist.view(flow=True)[1:-1], H) assert hist.axes[0].size == bins assert hist.axes[0].extent == bins + 2 @pytest.mark.parametrize("storage", methods) def test_2D_fill_int(storage): bins = (10, 15) ranges = ((0, 3), (0, 2)) vals = ((0.15, 0.25, 0.25), (0.35, 0.45, 0.45)) hist = bh.Histogram( bh.axis.Regular(bins[0], *ranges[0]), bh.axis.Regular(bins[1], *ranges[1]), storage=storage(), ) assert hist.storage_type == storage hist.fill(*vals) H = np.histogram2d(*vals, bins=bins, range=ranges)[0] assert_array_equal(np.asarray(hist), H) assert_array_equal(hist.view(flow=True)[1:-1, 1:-1], H) assert_array_equal(hist.view(flow=False), H) assert hist.axes[0].size == bins[0] assert hist.axes[0].extent == bins[0] + 2 assert hist.axes[1].size == bins[1] assert hist.axes[1].extent == bins[1] + 2 def test_edges_histogram(): edges = (1, 12, 22, 79) hist = bh.Histogram(bh.axis.Variable(edges), storage=bh.storage.Int64()) vals = (13, 15, 24, 29) hist.fill(vals) bins = np.asarray(hist) assert_array_equal(bins, [0, 2, 2]) assert_array_equal(hist.view(flow=True), [0, 0, 2, 2, 0]) assert_array_equal(hist.view(flow=False), [0, 2, 2]) def test_int_histogram(): hist = bh.Histogram(bh.axis.Integer(3, 7), storage=bh.storage.Int64()) vals = (1, 2, 3, 4, 5, 6, 7, 8, 9) hist.fill(vals) bins = np.asarray(hist) assert_array_equal(bins, [1, 1, 1, 1]) assert_array_equal(hist.view(flow=False), [1, 1, 1, 1]) assert_array_equal(hist.view(flow=True), [2, 1, 1, 1, 1, 3]) def test_str_categories_histogram(): hist = bh.Histogram( bh.axis.StrCategory(["a", "b", "c"]), storage=bh.storage.Int64() ) vals = ["a", "b", "b", "c"] hist.fill(vals) assert hist[bh.loc("a")] == 1 assert hist[bh.loc("b")] == 2 assert hist[bh.loc("c")] == 1 # Issue 715 def test_select_many(): hist = bh.Histogram( bh.axis.StrCategory(["a", "b"]), bh.axis.StrCategory(["x", "y", "z"]), bh.axis.Regular(10, 0, 1), ) pick_a = hist[bh.loc("a"), ...] with pytest.warns(UserWarning, match="List indexing selection is experimental"): pick_b = pick_a[[bh.loc("x"), bh.loc("y")], ...] with pytest.warns(UserWarning, match="List indexing selection is experimental"): pick = hist[bh.loc("a"), [bh.loc("x"), bh.loc("y")], ...] assert pick_b.axes[0] == pick.axes[0] assert len(pick_b.axes) == len(pick.axes) def test_growing_histogram(): hist = bh.Histogram( bh.axis.Regular(10, 0, 1, growth=True), storage=bh.storage.Int64() ) hist.fill(1.45) assert hist.size == 17 def test_numpy_dd(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Int64() ) for i in range(10): for j in range(5): x, y = h.axes[0].centers[i], h.axes[1].centers[j] v = i + j * 10 + 1 h.fill([x] * v, [y] * v) h2, x2, y2 = h.to_numpy() h1, (x1, y1) = h.to_numpy(dd=True) assert_array_equal(h1, h2) assert_array_equal(x1, x2) assert_array_equal(y1, y2) def test_numpy_weights(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Weight() ) for i in range(10): for j in range(5): x, y = h.axes[0].centers[i], h.axes[1].centers[j] v = i + j * 10 + 1 h.fill([x] * v, [y] * v) h2, x2, y2 = h.to_numpy(view=False) h1, (x1, y1) = h.to_numpy(dd=True, view=False) assert_array_equal(h1, h2) assert_array_equal(x1, x2) assert_array_equal(y1, y2) h1, (x1, y1) = h.to_numpy(dd=True, view=False) h2, x2, y2 = h.to_numpy(view=True) assert_array_equal(h1, h2.value) assert_array_equal(x1, x2) assert_array_equal(y1, y2) def test_numpy_flow(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Int64() ) for i in range(10): for j in range(5): x, y = h.axes[0].centers[i], h.axes[1].centers[j] v = i + j * 10 + 1 h.fill([x] * v, [y] * v) flow_true = h.to_numpy(True)[0][1:-1, 1:-1] flow_false = h.to_numpy(False)[0] assert_array_equal(flow_true, flow_false) view_flow_true = h.view(flow=True) view_flow_false = h.view(flow=False) view_flow_default = h.view() assert_array_equal(view_flow_true[1:-1, 1:-1], view_flow_false) assert_array_equal(view_flow_default, view_flow_false) def test_numpy_compare(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Int64() ) xs = [] ys = [] for i in range(10): for j in range(5): x, y = h.axes[0].centers[i], h.axes[1].centers[j] v = i + j * 10 + 1 xs += [x] * v ys += [y] * v h.fill(xs, ys) H, E1, E2 = h.to_numpy() nH, nE1, nE2 = np.histogram2d(xs, ys, bins=(10, 5), range=((0, 1), (0, 1))) assert_array_equal(H, nH) assert_allclose(E1, nE1) assert_allclose(E2, nE2) def test_project(): h = bh.Histogram( bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Int64() ) h0 = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=bh.storage.Int64()) h1 = bh.Histogram(bh.axis.Regular(5, 0, 1), storage=bh.storage.Int64()) for x, y in ( (0.3, 0.3), (0.7, 0.7), (0.5, 0.6), (0.23, 0.92), (0.15, 0.32), (0.43, 0.54), ): h.fill(x, y) h0.fill(x) h1.fill(y) assert h.project(0, 1) == h assert h.project(0) == h0 assert h.project(1) == h1 assert_array_equal(h.project(0, 1), h) assert_array_equal(h.project(0), h0) assert_array_equal(h.project(1), h1) def test_sums(): h = bh.Histogram(bh.axis.Regular(4, 0, 1)) h.fill([0.1, 0.2, 0.3, 10]) assert h.sum() == 3 assert h.sum(flow=True) == 4 def test_int_cat_hist(): h = bh.Histogram(bh.axis.IntCategory([1, 2, 3]), storage=bh.storage.Int64()) h.fill(1) h.fill(2) h.fill(3) assert_array_equal(h.view(), [1, 1, 1]) assert h.sum() == 3 with pytest.raises(TypeError): h.fill(0.5) @pytest.mark.filterwarnings("ignore:List indexing selection is experimental") def test_int_cat_hist_pick_several(): h = bh.Histogram( bh.axis.IntCategory([1, 2, 7], __dict__={"xval": 5}), storage=bh.storage.Int64() ) h.fill(1, weight=8) h.fill(2, weight=7) h.fill(7, weight=6) assert h.view() == approx(np.array([8, 7, 6])) assert h.sum() == 21 assert h[[0, 2]].values() == approx(np.array([8, 6])) assert h[[2, 0]].values() == approx(np.array([6, 8])) assert h[[1]].values() == approx(np.array([7])) assert h[[bh.loc(1), bh.loc(7)]].values() == approx(np.array([8, 6])) assert h[[bh.loc(7), bh.loc(1)]].values() == approx(np.array([6, 8])) assert h[[bh.loc(2)]].values() == approx(np.array([7])) assert tuple(h[[0, 2]].axes[0]) == (1, 7) assert tuple(h[[2, 0]].axes[0]) == (7, 1) assert tuple(h[[1]].axes[0]) == (2,) assert h.axes[0].xval == 5 assert h[[0]].axes[0].xval == 5 assert h[[0, 1, 2]].axes[0].xval == 5 @pytest.mark.filterwarnings("ignore:List indexing selection is experimental") def test_str_cat_pick_several(): h = bh.Histogram(bh.axis.StrCategory(["a", "b", "c"])) h.fill(["a", "a", "a", "b", "b", "c"], weight=0.25) assert h[[0, 1, 2]].values() == approx(np.array([0.75, 0.5, 0.25])) assert h[[2, 1, 0]].values() == approx(np.array([0.25, 0.5, 0.75])) assert h[[1, 0]].values() == approx(np.array([0.5, 0.75])) assert h[[bh.loc("a"), bh.loc("b"), bh.loc("c")]].values() == approx( np.array([0.75, 0.5, 0.25]) ) assert h[[bh.loc("c"), bh.loc("b"), bh.loc("a")]].values() == approx( np.array([0.25, 0.5, 0.75]) ) assert h[[bh.loc("b"), bh.loc("a")]].values() == approx(np.array([0.5, 0.75])) assert tuple(h[[1, 0]].axes[0]) == ("b", "a") @pytest.mark.filterwarnings("ignore:List indexing selection is experimental") def test_pick_invalid(): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) with pytest.raises(RuntimeError): h[[0, 1]] h = bh.Histogram(bh.axis.Integer(0, 10)) with pytest.raises(RuntimeError): h[[0, 1]] @pytest.mark.filterwarnings("ignore:List indexing selection is experimental") def test_str_cat_pick_dual(): h = bh.Histogram( bh.axis.StrCategory(["a", "b", "c"]), bh.axis.StrCategory(["d", "e", "f"]) ) vals = np.arange(9).reshape(3, 3) h.values()[...] = vals assert h[[0], [0]].values() == approx(vals[[0]][:, [0]]) assert h[[1], [2]].values() == approx(vals[[1]][:, [2]]) assert h[[1], [0, 1]].values() == approx(vals[[1]][:, [0, 1]]) assert h[[0, 1], [1]].values() == approx(vals[[0, 1]][:, [1]]) assert h[[0, 1], [0, 1]].values() == approx(vals[[0, 1]][:, [0, 1]]) assert h[[0, 1], [2, 1]].values() == approx(vals[[0, 1]][:, [2, 1]]) @pytest.mark.filterwarnings("ignore:List indexing selection is experimental") def test_pick_multiaxis(): h = bh.Histogram( bh.axis.StrCategory(["a", "b", "c"]), bh.axis.IntCategory([-5, 0, 10]), bh.axis.Regular(5, 0, 1), bh.axis.StrCategory(["d", "e", "f"]), storage=bh.storage.Int64(), ) h.fill("b", -5, 0.65, "f") h.fill("b", -5, 0.65, "e") mini = h[[bh.loc("b"), 2], [1, bh.loc(-5)], sum, bh.loc("f")] assert mini.ndim == 2 assert tuple(mini.axes[0]) == ("b", "c") assert tuple(mini.axes[1]) == (0, -5) assert h[[1, 2], [0, 1], sum, bh.loc("f")].sum() == 1 assert h[[1, 2], [1, 0], sum, bh.loc("f")].sum() == 1 assert mini.values() == approx(np.array(((0, 1), (0, 0)))) scikit-hep-boost-histogram-5f6f3e3/tests/test_minihist_title.py000066400000000000000000000121741516627434300251340ustar00rootroot00000000000000# The point of this test is to make sure that the infrastructure for supporting # custom attributes, like title in Hist, is working. from __future__ import annotations import pytest import boost_histogram as bh # First, make a new family to identify your library CUSTOM_FAMILY = object() # Add named axes class NamedAxesTuple(bh.axis.AxesTuple): __slots__ = () def _get_index_by_name(self, name): if not isinstance(name, str): return name for i, ax in enumerate(self): if ax.name == name: return i raise KeyError(f"{name} not found in axes") def __getitem__(self, item): if isinstance(item, slice): item = slice( self._get_index_by_name(item.start), self._get_index_by_name(item.stop), self._get_index_by_name(item.step), ) else: item = self._get_index_by_name(item) return super().__getitem__(item) @property def name(self): """ The names of the axes. May be empty strings. """ return tuple(ax.name for ax in self) @name.setter def name(self, values): for ax, val in zip(self, values, strict=False): ax._ax.raw_metadata["name"] = f"test: {val}" # When you subclass Histogram or an Axes, you should register your family so # boost-histogram will know what to convert C++ objects into. class AxesMixin: __slots__ = () # Only required for placing the Mixin first def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @property def name(self): """ Get the name for the Regular axis """ return self._ax.raw_metadata.get("name", "") # The order of the mixin is important here - it must be first # if it needs to override bh.axis.Regular, otherwise, last is simpler, # as it doesn't need to forward __init_subclass__ kwargs then. class Regular(bh.axis.Regular, AxesMixin, family=CUSTOM_FAMILY): __slots__ = () def __init__(self, bins, start, stop, name): super().__init__(bins, start, stop) self._ax.raw_metadata["name"] = name class Integer(AxesMixin, bh.axis.Integer, family=CUSTOM_FAMILY): __slots__ = () def __init__(self, start, stop, name): super().__init__(start, stop) self._ax.raw_metadata["name"] = name class CustomHist(bh.Histogram, family=CUSTOM_FAMILY): def _generate_axes_(self): return NamedAxesTuple(self._axis(i) for i in range(self.ndim)) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) valid_names = [ax.name for ax in self.axes if ax.name] if len(valid_names) != len(set(valid_names)): msg = f"{self.__class__.__name__} instance cannot contain axes with duplicated names" raise KeyError(msg) def test_hist_creation(): hist_1 = CustomHist(Regular(10, 0, 1, name="a"), Integer(0, 4, name="b")) assert hist_1.axes[0].name == "a" assert hist_1.axes[1].name == "b" hist_2 = CustomHist(Regular(10, 0, 1, name=""), Regular(20, 0, 4, name="")) assert not hist_2.axes[0].name assert not hist_2.axes[1].name with pytest.raises(KeyError): CustomHist(Regular(10, 0, 1, name="a"), Regular(20, 0, 4, name="a")) def test_hist_index(): hist_1 = CustomHist(Regular(10, 0, 1, name="a"), Regular(20, 0, 4, name="b")) assert hist_1.axes[0].name == "a" assert hist_1.axes[1].name == "b" def test_hist_convert(): hist_1 = CustomHist(Regular(10, 0, 1, name="a"), Integer(0, 4, name="b")) hist_bh = bh.Histogram(hist_1) assert type(hist_bh.axes[0]) is bh.axis.Regular assert type(hist_bh.axes[1]) is bh.axis.Integer assert hist_bh.axes[0].name == "a" assert hist_bh.axes[1].name == "b" hist_2 = CustomHist(hist_bh) assert type(hist_2.axes[0]) is Regular assert type(hist_2.axes[1]) is Integer assert hist_2.axes[0].name == "a" assert hist_2.axes[1].name == "b" # Just verify no-op status hist_3 = CustomHist(hist_1) assert type(hist_3.axes[0]) is Regular assert type(hist_3.axes[1]) is Integer assert hist_3.axes[0].name == "a" assert hist_3.axes[1].name == "b" def test_access(): hist = CustomHist(Regular(10, 0, 1, name="a"), Regular(20, 0, 4, name="b")) assert hist.axes["a"] == hist.axes[0] assert hist.axes["b"] == hist.axes[1] from_bh = bh.Histogram(bh.axis.Regular(10, 0, 1), bh.axis.Regular(20, 0, 4)) from_bh.axes.name = "a", "b" hist_conv = CustomHist(from_bh) assert hist_conv.axes["a"] == hist_conv.axes[0] assert hist_conv.axes["b"] == hist_conv.axes[1] def test_hist_name_set(): hist_1 = CustomHist(Regular(10, 0, 1, name="a"), Regular(20, 0, 4, name="b")) hist_1.axes.name = ("c", "d") assert hist_1.axes.name == ("test: c", "test: d") with pytest.raises(AttributeError): hist_1.axes[0].name = "a" hist_1.axes.label = ("one", "two") assert hist_1.axes.label == ("one", "two") with pytest.raises(ValueError): hist_1.axes.label = ("one",) with pytest.raises(ValueError): hist_1.axes.label = ("one", "two", "three") scikit-hep-boost-histogram-5f6f3e3/tests/test_numpy_interface.py000066400000000000000000000124501516627434300252740ustar00rootroot00000000000000from __future__ import annotations import copy import numpy as np import pytest from pytest import approx import boost_histogram as bh np113 = tuple(int(x) for x in np.__version__.split(".")[:2]) >= (1, 13) inputs_1d = ( [1, 2, 3, 4, 3, 4, 5, 10, 9, 11, 21, -2], [ 0.27237556, 0.72020987, 0.75204098, -0.29265003, -2.67332888, 0.68420365, -0.60629843, -0.6375687, -1.00017927, -0.07707552, ], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ) opts = ( {}, {"bins": 10}, {"bins": "auto" if np113 else 20}, {"range": (0, 5), "bins": 30}, {"range": np.array((0, 5), dtype=float), "bins": np.int32(30)}, {"range": np.array((0, 3), dtype=np.double), "bins": np.uint32(10)}, {"range": np.array((0, 10), dtype=int), "bins": np.int8(30)}, {"bins": [0, 1, 1.2, 1.3, 4, 21]}, ) @pytest.mark.parametrize("a", inputs_1d) @pytest.mark.parametrize("opt", opts) def test_histogram1d(a, opt): v = np.array(a) h1, e1 = np.histogram(v, **opt) h2, e2 = bh.numpy.histogram(v, **opt) assert e1 == approx(e2) assert h1 == approx(h2) opt = copy.deepcopy(opt) opt["density"] = True h1, e1 = np.histogram(v, **opt) h2, e2 = bh.numpy.histogram(v, **opt) assert e1 == approx(e2) assert h1 == approx(h2) @pytest.mark.parametrize("a", inputs_1d) @pytest.mark.parametrize("opt", opts) def test_histogram1d_object(a, opt): bh_opt = copy.deepcopy(opt) bh_opt["histogram"] = bh.Histogram v = np.array(a) h1, e1 = np.histogram(v, **opt) bh_h2 = bh.numpy.histogram(v, **bh_opt) h2, e2 = bh_h2.to_numpy() assert e1 == approx(e2) assert h1 == approx(h2) # Ensure reducible assert bh_h2[:5].values() == approx(h1[:5]) opt = copy.deepcopy(opt) opt["density"] = True bh_opt = copy.deepcopy(bh_opt) bh_opt["density"] = True with pytest.raises(KeyError): bh_h2 = bh.numpy.histogram(v, **bh_opt) def test_histogram2d(): x = np.array([0.3, 0.3, 0.1, 0.8, 0.34, 0.03, 0.32, 0.65]) y = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98]) h1, e1x, e1y = np.histogram2d(x, y) h2, e2x, e2y = bh.numpy.histogram2d(x, y) assert e1x == approx(e2x) assert e1y == approx(e2y) assert h1 == approx(h2) h1, e1x, e1y = np.histogram2d(x, y, density=True) h2, e2x, e2y = bh.numpy.histogram2d(x, y, density=True) assert e1x == approx(e2x) assert e1y == approx(e2y) assert h1 == approx(h2) def test_histogram2d_object(): x = np.array([0.3, 0.3, 0.1, 0.8, 0.34, 0.03, 0.32, 0.65]) y = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98]) h1, e1x, e1y = np.histogram2d(x, y) bh_h2 = bh.numpy.histogram2d(x, y, histogram=bh.Histogram) h2, e2x, e2y = bh_h2.to_numpy() assert e1x == approx(e2x) assert e1y == approx(e2y) assert h1 == approx(h2) with pytest.raises(KeyError): bh.numpy.histogram2d(x, y, density=True, histogram=bh.Histogram) def test_histogramdd(): x = np.array([0.3, 0.3, 0.1, 0.8, 0.34, 0.03, 0.32, 0.65]) y = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98]) z = np.array([0.5, 0.7, 0.0, 0.65, 0.72, 0.01, 0.3, 1.4]) h1, (e1x, e1y, e1z) = np.histogramdd([x, y, z]) h2, (e2x, e2y, e2z) = bh.numpy.histogramdd([x, y, z]) assert e1x == approx(e2x) assert e1y == approx(e2y) assert e1z == approx(e2z) assert h1 == approx(h2) h1, (e1x, e1y, e1z) = np.histogramdd([x, y, z], density=True) h2, (e2x, e2y, e2z) = bh.numpy.histogramdd([x, y, z], density=True) assert e1x == approx(e2x) assert e1y == approx(e2y) assert e1z == approx(e2z) assert h1 == approx(h2) def test_histogramdd_object(): x = np.array([0.3, 0.3, 0.1, 0.8, 0.34, 0.03, 0.32, 0.65]) y = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98]) z = np.array([0.5, 0.7, 0.0, 0.65, 0.72, 0.01, 0.3, 1.4]) h1, (e1x, e1y, e1z) = np.histogramdd([x, y, z]) bh_h2 = bh.numpy.histogramdd([x, y, z], histogram=bh.Histogram) h2, (e2x, e2y, e2z) = bh_h2.to_numpy(dd=True) assert e1x == approx(e2x) assert e1y == approx(e2y) assert e1z == approx(e2z) assert h1 == approx(h2) with pytest.raises(KeyError): bh.numpy.histogramdd([x, y, z], density=True, histogram=bh.Histogram) def test_histogram_weights(): x = np.array([0.3, 0.3, 0.1, 0.8, 0.34, 0.03, 0.32, 0.65]) weights = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98]) h1, edges = np.histogram(x, weights=weights) bh_h1, bh_edges = bh.numpy.histogram(x, weights=weights) assert bh_h1 == approx(h1) assert bh_edges == approx(edges) def test_histogram_nans(): x = np.array([0, 1, 2, 3, np.nan]) with pytest.raises(ValueError): np.histogram(x) with pytest.raises(ValueError): bh.numpy.histogram(x) def test_histogram_all_zeros(): x = np.array([0, 0, 0, 0, 0, 0]) h1, edges = np.histogram(x) bh_h1, bh_edges = bh.numpy.histogram(x) assert bh_h1 == approx(h1) assert bh_edges == approx(edges) def test_histogram_all_ones(): x = np.array([0, 0, 0, 0, 0, 0]) h1, edges = np.histogram(x) bh_h1, bh_edges = bh.numpy.histogram(x) assert bh_h1 == approx(h1) assert bh_edges == approx(edges) scikit-hep-boost-histogram-5f6f3e3/tests/test_pandaslike.py000066400000000000000000000021231516627434300242130ustar00rootroot00000000000000from __future__ import annotations import numpy as np import boost_histogram as bh # This is not a reasonable approximation of Pandas, but rather a test for an # arbitrary convertible object (but if this works, so does Pandas) class Seriesish: def __init__(self, array): self.array = np.asarray(array) def __array__(self, dtype=None, *, copy=None): # Copy is new in NumPy 2.0 kw = {} if copy is not None: kw["copy"] = copy return np.asarray(self.array, dtype=dtype, **kw) def test_setting_weighted_profile_convertable(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.WeightedMean()) data = Seriesish([0.3, 0.3, 0.4, 1.2, 1.6]) samples = Seriesish([1, 2, 3, 4, 4]) weights = Seriesish([1, 1, 1, 1, 2]) h.fill(data, sample=samples, weight=weights) assert h[0] == bh.accumulators.WeightedMean( sum_of_weights=3, sum_of_weights_squared=3, value=2, variance=1 ) assert h[1] == bh.accumulators.WeightedMean( sum_of_weights=3, sum_of_weights_squared=5, value=4, variance=0 ) scikit-hep-boost-histogram-5f6f3e3/tests/test_pbt.py000066400000000000000000000020671516627434300226740ustar00rootroot00000000000000from __future__ import annotations import numpy as np import pytest from pytest import approx import boost_histogram as bh hypothesis = pytest.importorskip("hypothesis") nst = pytest.importorskip("hypothesis.extra.numpy") @hypothesis.given( nst.arrays( float, (4,), elements={ "min_value": 1, "max_value": 100, "exclude_min": True, "allow_nan": False, }, ), nst.arrays( float, (4,), elements={"min_value": -100, "max_value": 100, "allow_nan": False} ), nst.arrays( float, (4,), elements={ "min_value": 0, "max_value": 100, "allow_nan": False, "exclude_min": True, }, ), ) def test_variance_setting(cnt, val, var): h = bh.Histogram(bh.axis.Regular(4, 0, 1), storage=bh.storage.Mean()) h[...] = np.stack([cnt, val, var * cnt], axis=-1) assert h.counts() == approx(cnt) assert h.values() == approx(val) assert h.variances() == approx(var) # , abs=1e-3, rel=1e-3) scikit-hep-boost-histogram-5f6f3e3/tests/test_pickle.py000066400000000000000000000172201516627434300233530ustar00rootroot00000000000000from __future__ import annotations import copy import ctypes import math import sys from pickle import dumps, loads import numpy as np import pytest from pytest import approx import boost_histogram as bh ftype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double) def python_convert(x): return ftype(x) def pickle_roundtrip_protocol_2(x): return loads(dumps(x, 2)) def pickle_roundtrip_protocol_highest(x): return loads(dumps(x, -1)) @pytest.fixture( params=( pickle_roundtrip_protocol_2, pickle_roundtrip_protocol_highest, copy.copy, copy.deepcopy, ) ) def copy_fn(request): return request.param @pytest.mark.parametrize( "opts", [{}, {"growth": True}, {"underflow": True, "overflow": True}] ) def test_options(copy_fn, opts): orig = bh.axis.Traits(**opts) new = copy_fn(orig) assert new == orig @pytest.mark.parametrize( ("accum", "args"), [ (bh.accumulators.Sum, (12,)), (bh.accumulators.WeightedSum, (1.5, 2.5)), (bh.accumulators.Mean, (5, 1.5, 2.5)), (bh.accumulators.WeightedMean, (1.5, 2.5, 3.5, 4.5)), ], ) def test_accumulators(accum, args, copy_fn): orig = accum(*args) new = copy_fn(orig) assert new == orig axes_creations = [ (bh.axis.Regular, (4, 2, 4), {"underflow": False, "overflow": False}), (bh.axis.Regular, (4, 2, 4), {}), (bh.axis.Regular, (4, 2, 4), {"growth": True}), (bh.axis.Regular, (4, 2, 4), {"transform": bh.axis.transform.log}), (bh.axis.Regular, (4, 2, 4), {"transform": bh.axis.transform.sqrt}), (bh.axis.Regular, (4, 2, 4), {"transform": bh.axis.transform.Pow(0.5)}), (bh.axis.Regular, (4, 2, 4), {"circular": True}), (bh.axis.Variable, ([1, 2, 3, 4],), {}), (bh.axis.Variable, ([1, 2, 3, 4],), {"circular": True}), (bh.axis.Integer, (1, 4), {}), (bh.axis.Integer, (1, 4), {"circular": True}), (bh.axis.IntCategory, ([1, 2, 3],), {}), (bh.axis.IntCategory, ([1, 2, 3],), {"growth": True}), (bh.axis.StrCategory, (["1", "2", "3"],), {}), (bh.axis.StrCategory, (["1", "2", "3"],), {"growth": True}), ] @pytest.mark.parametrize(("axis", "args", "opts"), axes_creations) def test_axes(axis, args, opts, copy_fn): orig = axis(*args, **opts) new = copy_fn(orig) assert new == orig assert new.centers == approx(orig.centers) @pytest.mark.parametrize(("axis", "args", "opts"), axes_creations) def test_metadata_str(axis, args, opts, copy_fn): orig = axis(*args, **opts) orig.metadata = "foo" new = copy_fn(orig) assert new.metadata == orig.metadata new.metadata = orig.metadata assert new == orig assert new.centers == approx(orig.centers) # Special test: Deepcopy should change metadata id, copy should not def test_compare_copy_axis(metadata): orig = bh.axis.Regular(4, 0, 2, metadata=metadata) new = copy.copy(orig) dnew = copy.deepcopy(orig) assert orig.metadata is new.metadata assert orig.metadata == dnew.metadata if metadata is not copy.copy(metadata): assert orig.metadata is not dnew.metadata # Special test: Deepcopy should change metadata id, copy should not def test_compare_copy_hist(metadata): orig = bh.Histogram(bh.axis.Regular(4, 0, 2, metadata=metadata)) new = copy.copy(orig) dnew = copy.deepcopy(orig) assert orig.axes[0].metadata is new.axes[0].metadata assert orig.axes[0].metadata == dnew.axes[0].metadata if metadata is not copy.copy(metadata): assert orig.axes[0].metadata is not dnew.axes[0].metadata @pytest.mark.parametrize(("axis", "args", "opts"), axes_creations) def test_metadata_any(axis, args, opts, copy_fn): orig = axis(*args, **opts) orig.metadata = (1, 2, 3) new = copy_fn(orig) assert new.metadata == orig.metadata new.metadata = orig.metadata assert new == orig @pytest.mark.benchmark(group="histogram-pickling") @pytest.mark.parametrize( ("storage", "extra"), [ (bh.storage.AtomicInt64, {}), (bh.storage.Int64, {}), (bh.storage.Unlimited, {}), (bh.storage.Unlimited, {"weight"}), (bh.storage.Double, {"weight"}), (bh.storage.Weight, {"weight"}), (bh.storage.Mean, {"sample"}), (bh.storage.WeightedMean, {"weight", "sample"}), (bh.storage.MultiCell, {"MultiCell"}), ], ) def test_storage(benchmark, copy_fn, storage, extra): n = 1000 init_list = [] if extra == {"MultiCell"}: init_list.append(3) hist = bh.Histogram(bh.axis.Integer(0, n), storage=storage(*init_list)) x = np.arange(2 * (n + 2)) % (n + 2) - 1 if extra == {}: hist.fill(x) elif extra == {"weight"}: hist.fill(x, weight=np.arange(2 * n + 4) + 1) elif extra == {"sample"}: hist.fill(x, sample=np.arange(2 * n + 4) + 1) elif extra == {"MultiCell"}: hist.fill(x, weight=np.reshape(np.arange(3 * (2 * n + 4)) + 1, (2 * n + 4, 3))) else: hist.fill(x, weight=np.arange(2 * n + 4) + 1, sample=np.arange(2 * n + 4) + 1) new = benchmark(copy_fn, hist) assert np.asarray(hist.view(True)) == approx(np.asarray(new.view(True))) assert new == hist def test_histogram_regular(copy_fn): hist = bh.Histogram(bh.axis.Regular(4, 1, 2), bh.axis.Regular(8, 3, 6)) new = copy_fn(hist) assert hist == new def test_histogram_fancy(copy_fn): hist = bh.Histogram(bh.axis.Regular(4, 1, 2), bh.axis.Integer(0, 6)) new = copy_fn(hist) assert hist == new def test_histogram_metadata(copy_fn, metadata): hist = bh.Histogram(bh.axis.Regular(4, 1, 2, metadata=metadata)) new = copy_fn(hist) assert hist == new @pytest.mark.skipif( sys.implementation.name == "pypy", reason="Not remotely supported on PyPY, hangs forever", ) @pytest.mark.skipif( sys.implementation.name == "graalpy", reason="Not supported, throws exception", ) @pytest.mark.parametrize("mod", [np, math]) def test_pickle_transforms(mod, copy_fn): ax1 = bh.axis.Regular( 100, 1, 100, transform=bh.axis.transform.Function(mod.log, mod.exp, convert=python_convert), ) ax2 = copy_fn(ax1) ax3 = bh.axis.Regular(100, 1, 100, transform=bh.axis.transform.log) assert ax1 == ax2 assert ax1.centers == approx(ax2.centers) assert ax2.centers == approx(ax3.centers) def test_hist_axes_reference(copy_fn): h = bh.Histogram(bh.axis.Regular(10, 0, 1, metadata=1)) h.axes[0].metadata = 2 h2 = copy_fn(h) assert h2._hist is not h._hist assert h2.axes[0] is not h.axes[0] assert h2.axes[0].metadata == 2 h.axes[0].metadata = 3 assert h2._axis(0).metadata == 2 assert h2.axes[0].metadata == 2 def test_hist_axes_reference_arbitrary(copy_fn): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h.other = 2 h2 = copy_fn(h) assert h2._hist is not h._hist assert h2.other == 2 h.other = 3 assert h2.other == 2 def test_hist_reference_arbitrary(copy_fn): h = bh.Histogram(bh.axis.Regular(10, 0, 1)) h.axes[0].other = 2 h2 = copy_fn(h) assert h2._hist is not h._hist assert h2.axes[0] is not h.axes[0] assert h2.axes[0].other == 2 h.axes[0].other = 3 assert h2._axis(0).other == 2 assert h2.axes[0].other == 2 def test_axis_wrapped(copy_fn): ax = bh.axis.Regular(10, 0, 2) ax2 = copy_fn(ax) assert ax._ax is not ax2._ax def test_trans_wrapped(copy_fn): tr = bh.axis.transform.Pow(2) tr2 = copy_fn(tr) assert tr._this is not tr2._this # Testing #342 def test_cloudpickle(): cloudpickle = pytest.importorskip("cloudpickle") h = bh.Histogram(bh.axis.Regular(50, 0, 20)) h.fill([1, 2, 3, 4, 5]) h2 = loads(cloudpickle.dumps(h)) assert h == h2 assert h is not h2 scikit-hep-boost-histogram-5f6f3e3/tests/test_pickles.py000066400000000000000000000032231516627434300235340ustar00rootroot00000000000000from __future__ import annotations import pickle from pathlib import Path import pytest import boost_histogram as bh DIR = Path(__file__).parent @pytest.mark.parametrize("version", ["0.10.2", "0.6.2", "0.11.1", "1.1.0"]) def test_read_pickle(version): filename = DIR / "pickles" / f"bh_{version}.pkl" with filename.open("rb") as f: d = pickle.load(f) assert d["version"] == version h1 = d["h1"] h2 = d["h2"] h3 = d["h3"] assert h1.storage_type == ( bh.storage.Double if version[0] == "0" else bh.storage.Int64 ) assert h2.storage_type == bh.storage.Weight assert h3.storage_type == bh.storage.Double assert h1[bh.loc(-5)] == 1 assert h1[bh.loc(1)] == 2 assert h1[bh.loc(2)] == 1 assert h2[0].value == 0 assert h2[1].value == 1 assert h2[2].value == 2 assert h2[3].value == 3 assert h2[4].value == 0 assert h3[bh.loc("one"), bh.loc(3)] == 1 assert h3[bh.loc("two"), bh.loc(2)] == 1 assert h3[bh.loc("two"), bh.loc(1)] == 1 assert h3[bh.loc("two"), bh.loc(3)] == 0 assert h3[bh.loc("two"), sum] == 2 assert isinstance(h1.axes[0], bh.axis.Regular) assert isinstance(h2.axes[0], bh.axis.Integer) assert isinstance(h3.axes[0], bh.axis.StrCategory) assert isinstance(h3.axes[1], bh.axis.Variable) assert h3.axes[0].traits.growth assert not h3.axes[1].traits.growth assert h1.axes[0].metadata is None assert h2.axes[0].metadata == {"hello": "world"} ver = tuple(map(int, version.split("."))) assert h1.metadata is None if ver < (0, 9, 0): assert h2.metadata is None else: assert h2.metadata == "foo" scikit-hep-boost-histogram-5f6f3e3/tests/test_plottable_protocol.py000066400000000000000000000061621516627434300260160ustar00rootroot00000000000000from __future__ import annotations import numpy as np from numpy.testing import assert_allclose import boost_histogram as bh COUNTS = np.array([3, 4, 5, 6]) VALUES = np.array([1, 2, 3, 4]) VARIANCES = np.array([0.1, 0.1, 0.2, 0.2]) SUM2 = np.array([9, 8, 7, 6]) # not in protocol, so not checked def test_plottable_histogram_mean_int(): h = bh.Histogram(bh.axis.Integer(0, 4), storage=bh.storage.Mean()) h[...] = np.stack([COUNTS, VALUES, VARIANCES]).T assert_allclose(COUNTS, h.counts()) assert_allclose(VALUES, h.values()) assert_allclose(VARIANCES / COUNTS, h.variances()) assert h.kind == bh.Kind.MEAN assert h.kind == "MEAN" assert len(h.axes) == 1 assert h.axes[0] == h.axes[0] assert h.axes[0][0] == 0 assert h.axes[0][1] == 1 assert h.axes[0][2] == 2 assert h.axes[0][3] == 3 assert h.axes[0].traits.discrete assert not h.axes[0].traits.circular def test_plottible_histogram_weight_reg(): h = bh.Histogram(bh.axis.Regular(4, 0, 4), storage=bh.storage.Weight()) h[...] = np.stack([VALUES, VARIANCES]).T assert_allclose(VALUES, h.counts()) assert_allclose(VALUES, h.values()) assert_allclose(VARIANCES, h.variances()) assert h.kind == bh.Kind.COUNT assert h.kind == "COUNT" assert len(h.axes) == 1 assert h.axes[0] == h.axes[0] assert_allclose(h.axes[0][0], (0, 1)) assert_allclose(h.axes[0][1], (1, 2)) assert_allclose(h.axes[0][2], (2, 3)) assert_allclose(h.axes[0][3], (3, 4)) assert not h.axes[0].traits.discrete assert not h.axes[0].traits.circular def test_plottible_histogram_simple_var(): h = bh.Histogram(bh.axis.Variable([0, 1, 2, 3, 4])) # Setting directly or filling with weight should cause variances to # be None. Manipulations directly on the view, however, are up to the user # - once you've asked for a view of the memory, you should know what your # are doing. At least if you change it inplace. h.view()[...] = VALUES assert_allclose(VALUES, h.counts()) assert_allclose(VALUES, h.values()) assert_allclose(VALUES, h.variances()) assert h.kind == bh.Kind.COUNT assert h.kind == "COUNT" assert len(h.axes) == 1 assert h.axes[0] == h.axes[0] assert_allclose(h.axes[0][0], (0, 1)) assert_allclose(h.axes[0][1], (1, 2)) assert_allclose(h.axes[0][2], (2, 3)) assert_allclose(h.axes[0][3], (3, 4)) assert not h.axes[0].traits.discrete assert not h.axes[0].traits.circular h.fill([1], weight=0) assert_allclose(VALUES, h.counts()) assert_allclose(VALUES, h.values()) assert h.variances() is None def test_plottible_histogram_simple_var_invalidate_inplace(): h = bh.Histogram(bh.axis.Variable([0, 1, 2, 3, 4])) h.view()[...] = VALUES h2 = h * 1 assert_allclose(VALUES, h.counts()) assert_allclose(VALUES, h.values()) assert_allclose(VALUES, h.variances()) assert_allclose(VALUES, h2.counts()) assert_allclose(VALUES, h2.values()) assert h2.variances() is None h *= 1 assert_allclose(VALUES, h.counts()) assert_allclose(VALUES, h.values()) assert h.variances() is None scikit-hep-boost-histogram-5f6f3e3/tests/test_profiles.py000066400000000000000000000046231516627434300237320ustar00rootroot00000000000000from __future__ import annotations import numpy as np from pytest import approx import boost_histogram as bh def test_mean_hist(): h = bh.Histogram(bh.axis.Regular(3, 0, 1), storage=bh.storage.Mean()) h.fill(0.10, sample=[2.5]) h.fill(0.25, sample=[3.5]) h.fill(0.45, sample=[1.2]) h.fill(0.51, sample=[3.4]) h.fill(0.81, sample=[1.3]) h.fill(0.86, sample=[1.9]) results = ( {"count": 2, "value": 3.0, "variance": 0.5}, {"count": 2, "value": 2.3, "variance": 2.42}, {"count": 2, "value": 1.6, "variance": 0.18}, ) for i in range(len(h.axes[0])): assert results[i]["count"] == h[i].count assert results[i]["value"] == approx(h[i].value) assert results[i]["variance"] == approx(h[i].variance) def test_mean_hist_scalar_axis_broadcast(): """Test that a scalar axis value is broadcast to match a sample array (issue #727).""" data = np.array([[1, 2, 3, 4, 5], [10, 20, 30, 40, 50], [100, 200, 300, 400, 500]]) h = bh.Histogram(bh.axis.Integer(0, 3), storage=bh.storage.Mean()) h.fill(0, sample=data[0]) h.fill(1, sample=data[1]) h.fill(2, sample=data[2]) assert h[0].count == 5 assert h[0].value == approx(3.0) assert h[1].count == 5 assert h[1].value == approx(30.0) assert h[2].count == 5 assert h[2].value == approx(300.0) # Verify equivalence with the explicit workaround h2 = bh.Histogram(bh.axis.Integer(0, 3), storage=bh.storage.Mean()) h2.fill([0] * len(data[0]), sample=data[0]) h2.fill([1] * len(data[1]), sample=data[1]) h2.fill([2] * len(data[2]), sample=data[2]) for i in range(3): assert h[i].count == h2[i].count assert h[i].value == approx(h2[i].value) assert h[i].variance == approx(h2[i].variance) def test_weighted_mean_hist_scalar_axis_broadcast(): """Test scalar axis broadcast for WeightedMean storage (issue #727).""" data = np.array([[1, 2, 3, 4, 5], [10, 20, 30, 40, 50], [100, 200, 300, 400, 500]]) h = bh.Histogram(bh.axis.Integer(0, 3), storage=bh.storage.WeightedMean()) h.fill(0, sample=data[0]) h.fill(1, sample=data[1]) h.fill(2, sample=data[2]) assert h[0].sum_of_weights == approx(5.0) assert h[0].value == approx(3.0) assert h[1].sum_of_weights == approx(5.0) assert h[1].value == approx(30.0) assert h[2].sum_of_weights == approx(5.0) assert h[2].value == approx(300.0) scikit-hep-boost-histogram-5f6f3e3/tests/test_serialization_uhi.py000066400000000000000000000431721516627434300256330ustar00rootroot00000000000000from __future__ import annotations import json import numpy as np import pytest import boost_histogram as bh from boost_histogram.serialization import from_uhi, remove_writer_info, to_uhi @pytest.mark.parametrize( ("storage_type", "expected_type"), [ pytest.param(bh.storage.AtomicInt64(), "int", id="atomic_int"), pytest.param(bh.storage.Int64(), "int", id="int"), pytest.param( bh.storage.Unlimited(), "double", id="unlimited" ), # This always renders as double pytest.param(bh.storage.Double(), "double", id="double"), ], ) def test_simple_to_dict(storage_type: bh.storage.Storage, expected_type: str) -> None: h = bh.Histogram( bh.axis.Regular(10, 0, 1), storage=storage_type, ) data = to_uhi(h) assert data["axes"][0]["type"] == "regular" assert data["axes"][0]["lower"] == 0 assert data["axes"][0]["upper"] == 1 assert data["axes"][0]["bins"] == 10 assert data["axes"][0]["underflow"] assert data["axes"][0]["overflow"] assert not data["axes"][0]["circular"] assert data["storage"]["type"] == expected_type assert data["storage"]["values"] == pytest.approx(np.zeros(12)) assert data["uhi_schema"] == 1 def test_weighed_to_dict() -> None: h = bh.Histogram( bh.axis.Integer(3, 15), storage=bh.storage.Weight(), ) data = to_uhi(h) assert data["axes"][0]["type"] == "regular" assert data["axes"][0]["lower"] == 3 assert data["axes"][0]["upper"] == 15 assert data["axes"][0]["bins"] == 12 assert data["axes"][0]["underflow"] assert data["axes"][0]["overflow"] assert not data["axes"][0]["circular"] assert data["storage"]["type"] == "weighted" assert data["storage"]["values"] == pytest.approx(np.zeros(14)) assert data["storage"]["variances"] == pytest.approx(np.zeros(14)) def test_mean_to_dict() -> None: h = bh.Histogram( bh.axis.StrCategory(["one", "two", "three"]), storage=bh.storage.Mean(), ) h.name = "hi" data = to_uhi(h) assert data["metadata"]["name"] == "hi" assert data["axes"][0]["type"] == "category_str" assert data["axes"][0]["categories"] == ["one", "two", "three"] assert data["axes"][0]["flow"] assert data["storage"]["type"] == "mean" assert data["storage"]["counts"] == pytest.approx(np.zeros(4)) assert data["storage"]["values"] == pytest.approx(np.zeros(4)) assert data["storage"]["variances"] == pytest.approx(np.zeros(4)) def test_weighted_mean_to_dict() -> None: h = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), storage=bh.storage.WeightedMean(), ) h.fill([1, 2, 3, 50], weight=[10, 20, 30, 5], sample=[100, 200, 300, 1]) h.fill([1, 2, 3, -3], weight=[10, 20, 30, 5], sample=[100, 200, 300, 1]) data = to_uhi(h) assert data["axes"][0]["type"] == "category_int" assert data["axes"][0]["categories"] == pytest.approx([1, 2, 3]) assert data["axes"][0]["flow"] assert data["storage"]["type"] == "weighted_mean" assert data["storage"]["sum_of_weights"] == pytest.approx( np.array([20, 40, 60, 10]) ) assert data["storage"]["sum_of_weights_squared"] == pytest.approx( np.array([200, 800, 1800, 50]) ) assert data["storage"]["values"] == pytest.approx(np.array([100, 200, 300, 1])) assert data["storage"]["variances"] == pytest.approx(np.zeros(4)) def test_transform_log_axis_to_dict() -> None: h = bh.Histogram(bh.axis.Regular(10, 1, 10, transform=bh.axis.transform.log)) data = to_uhi(h) assert data["axes"][0]["type"] == "variable" assert data["axes"][0]["edges"] == pytest.approx( np.exp(np.linspace(0, np.log(10), 11)) ) def test_transform_sqrt_axis_to_dict() -> None: h = bh.Histogram(bh.axis.Regular(10, 0, 10, transform=bh.axis.transform.sqrt)) data = to_uhi(h) assert data["axes"][0]["type"] == "variable" assert data["axes"][0]["edges"] == pytest.approx( (np.linspace(0, np.sqrt(10), 11)) ** 2 ) @pytest.mark.parametrize( "storage_type", [ pytest.param(bh.storage.AtomicInt64(), id="atomic_int"), pytest.param(bh.storage.Int64(), id="int"), pytest.param(bh.storage.Double(), id="double"), pytest.param(bh.storage.Unlimited(), id="unlimited"), ], ) def test_round_trip_simple(storage_type: bh.storage.Storage) -> None: h = bh.Histogram( bh.axis.Regular(10, 0, 10), storage=storage_type, ) h.fill([-1, 0, 0, 1, 20, 20, 20]) data = to_uhi(h) h2 = from_uhi(data) if isinstance(storage_type, (bh.storage.Int64, bh.storage.Double)): assert h == h2 assert h.view() == pytest.approx(h2.view()) def test_round_trip_weighted() -> None: h = bh.Histogram( bh.axis.Variable([1, 2, 4, 5], circular=True), storage=bh.storage.Weight(), ) h.fill(["1", "2", "3"], weight=[10, 20, 30]) h.fill(["1", "2", "3"], weight=[10, 20, 30]) data = to_uhi(h) h2 = from_uhi(data) assert pytest.approx(np.array(h.axes[0])) == np.array(h2.axes[0]) assert np.asarray(h) == pytest.approx(h2) def test_round_trip_mean() -> None: h = bh.Histogram( bh.axis.StrCategory(["1", "2", "3"]), storage=bh.storage.Mean(), ) h.fill(["1", "2", "3"], weight=[10, 20, 30], sample=[100, 200, 300]) h.fill(["1", "2", "3"], weight=[10, 20, 30], sample=[100, 200, 300]) data = to_uhi(h) h2 = from_uhi(data) assert pytest.approx(np.array(h.axes[0])) == np.array(h2.axes[0]) assert np.asarray(h) == pytest.approx(h2) def test_round_trip_weighted_mean() -> None: h = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), storage=bh.storage.WeightedMean(), ) h.fill([1, 2, 3], weight=[10, 20, 30], sample=[100, 200, 300]) h.fill([1, 2, 3], weight=[10, 20, 30], sample=[100, 200, 300]) data = to_uhi(h) h2 = from_uhi(data) assert pytest.approx(np.array(h.axes[0])) == np.array(h2.axes[0]) assert np.asarray(h) == pytest.approx(h2) def test_uhi_wrapper(): h = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), storage=bh.storage.WeightedMean(), ) assert to_uhi(h).keys() == h._to_uhi_().keys() data = h._to_uhi_() assert repr(from_uhi(data)) == repr(bh.Histogram._from_uhi_(data)) def test_uhi_direct_conversion(): h = bh.Histogram( bh.axis.IntCategory([1, 2, 3]), storage=bh.storage.Int64(), ) uhi_dict = h._to_uhi_() h2 = bh.Histogram(uhi_dict) assert h == h2 def test_round_trip_native() -> None: h = bh.Histogram( bh.axis.Integer(0, 10), storage=bh.storage.AtomicInt64(), ) h.fill([-1, 0, 0, 1, 20, 20, 20]) data = to_uhi(h) h2 = from_uhi(data) assert h == h2 @pytest.mark.parametrize( ("storage_type", "expected_type"), [ pytest.param(bh.storage.Int64(), "int", id="int64"), pytest.param(bh.storage.AtomicInt64(), "int", id="atomic_int64"), pytest.param(bh.storage.Double(), "double", id="double"), pytest.param(bh.storage.Weight(), "weighted", id="weight"), pytest.param(bh.storage.Mean(), "mean", id="mean"), ], ) def test_to_uhi_keep_storage_option( storage_type: bh.storage.Storage, expected_type: str ) -> None: h = bh.Histogram( bh.axis.Regular(3, 0, 1), storage=storage_type, ) data_with = to_uhi(h) data_without = to_uhi(h, keep_storage=False) assert "storage" in data_with assert "storage" in data_without # Storage with data has "values" key assert "values" in data_with["storage"] # Storage without data has only type information assert "values" not in data_without["storage"] assert data_without["storage"]["type"] == expected_type @pytest.mark.parametrize( "storage_type", [ pytest.param(bh.storage.Int64(), id="int64"), pytest.param(bh.storage.AtomicInt64(), id="atomic_int64"), pytest.param(bh.storage.Double(), id="double"), pytest.param(bh.storage.Weight(), id="weight"), pytest.param(bh.storage.Mean(), id="mean"), ], ) def test_from_uhi_missing_storage_data(storage_type: bh.storage.Storage) -> None: h = bh.Histogram( bh.axis.Regular(4, 0.0, 1.0), storage=storage_type, ) # produce a UHI dict with storage type but no data data = to_uhi(h, keep_storage=False) h2 = from_uhi(data) # axes and storage type should round-trip, data should be zeros assert pytest.approx(np.array(h.axes[0])) == np.array(h2.axes[0]) assert h2.storage_type is type(storage_type) assert np.asarray(h2) == pytest.approx(np.zeros_like(np.asarray(h2))) @pytest.mark.parametrize( ("storage_type", "expected_type"), [ pytest.param(bh.storage.AtomicInt64(), "int", id="atomic_int64"), pytest.param(bh.storage.Unlimited(), "double", id="unlimited"), ], ) def test_from_uhi_old_style_writer_info( storage_type: bh.storage.Storage, expected_type: str ) -> None: h = bh.Histogram( bh.axis.Regular(4, 0.0, 1.0), storage=storage_type, ) h.fill([0, 0, 1, 2]) data = to_uhi(h) old_style_data = { "uhi_schema": 1, "axes": data["axes"], "storage": { "type": expected_type, "values": data["storage"]["values"], "writer_info": { "boost-histogram": {"orig_type": type(storage_type).__name__} }, }, "metadata": data.get("metadata", {}), } h2 = from_uhi(old_style_data) assert h == h2 assert h2.storage_type is type(storage_type) @pytest.mark.parametrize( ("axis_type", "axis_args", "fill_values"), [ pytest.param(bh.axis.Integer, (0, 10), [-1, 0, 0, 1, 20, 20, 20], id="integer"), pytest.param( bh.axis.Regular, (10, 0.0, 10.0), [-1.0, 0.0, 0.0, 1.0, 20.0, 20.0, 20.0], id="regular", ), pytest.param( bh.axis.Variable, ([0.0, 2.0, 5.0, 10.0],), [-1.0, 0.0, 1.0, 6.0, 20.0, 20.0], id="variable", ), pytest.param( bh.axis.IntCategory, ([1, 2, 3],), [0, 1, 1, 2, 10, 10, 10], id="intcategory", ), pytest.param( bh.axis.StrCategory, (["A", "B", "C"],), ["Z", "A", "A", "B", "X", "X", "X"], id="strcategory", ), ], ) def test_round_trip_growth( axis_type: type, axis_args: tuple, fill_values: list ) -> None: h = bh.Histogram( axis_type(*axis_args, growth=True), ) h.fill(fill_values) data = to_uhi(h) h2 = from_uhi(data) assert h == h2 assert isinstance(h2.axes[0], axis_type) assert h2.axes[0].traits.growth == h.axes[0].traits.growth @pytest.mark.parametrize("remove", ["boost-histogram", None]) def test_round_trip_clean(remove: str | None) -> None: h = bh.Histogram( bh.axis.Integer(0, 10), storage=bh.storage.AtomicInt64(), ) h.fill([-1, 0, 0, 1, 20, 20, 20]) data = to_uhi(h) data = remove_writer_info(data, library=remove) h2 = from_uhi(data) assert isinstance(h2.axes[0], bh.axis.Regular) assert h2.storage_type is bh.storage.Int64 def test_unserializable_metadata() -> None: h = bh.Histogram( bh.axis.Integer(0, 10, __dict__={"c": 3, "@d": 4}), ) h.__dict__["a"] = 1 h.__dict__["@b"] = 2 data = to_uhi(h) assert data["metadata"] == {"a": 1, "_variance_known": True} assert data["axes"][0]["metadata"] == {"c": 3} def test_histogram_metadata() -> None: h = bh.Histogram(bh.axis.Integer(0, 10)) h.name = "Hi" h.label = "hi" h.other = 3 data = to_uhi(h) assert data["metadata"] == { "name": "Hi", "label": "hi", "other": 3, "_variance_known": True, } def test_remove_writer_info() -> None: d = { "uhi_schema": 1, "writer_info": {"boost-histogram": {"foo": "bar"}, "hist": {"FOO": "BAR"}}, } assert remove_writer_info(d, library=None) == {"uhi_schema": 1} assert remove_writer_info(d) == { "uhi_schema": 1, "writer_info": {"hist": {"FOO": "BAR"}}, } assert remove_writer_info(d, library="boost-histogram") == { "uhi_schema": 1, "writer_info": {"hist": {"FOO": "BAR"}}, } assert remove_writer_info(d, library="hist") == { "uhi_schema": 1, "writer_info": {"boost-histogram": {"foo": "bar"}}, } assert remove_writer_info(d, library="c") == d def test_convert_weight() -> None: h = bh.Histogram( bh.axis.Regular(3, 13, 10, __dict__={"name": "x"}), bh.axis.StrCategory(["one", "two"]), storage=bh.storage.Weight(), ) data = h._to_uhi_() h2 = bh.Histogram(data) assert h == h2 def test_convert_weightmean() -> None: h = bh.Histogram( bh.axis.Regular(12, 0, 1), bh.axis.StrCategory(["a", "b", "c", "d", "e", "f", "g"]), bh.axis.Boolean(), bh.axis.Integer(1, 18), storage=bh.storage.WeightedMean(), ) data = h._to_uhi_() h2 = bh.Histogram(data) assert h.axes == h2.axes def _json_round_trip(data: dict) -> dict: """Simulate a JSON round-trip, which can collapse empty array dimensions.""" return json.loads( json.dumps( data, default=lambda x: x.tolist() if isinstance(x, np.ndarray) else x, ) ) @pytest.mark.parametrize( "storage_type", [ pytest.param(bh.storage.Double(), id="double"), pytest.param(bh.storage.Int64(), id="int64"), pytest.param(bh.storage.Weight(), id="weight"), pytest.param(bh.storage.Mean(), id="mean"), pytest.param(bh.storage.WeightedMean(), id="weighted_mean"), ], ) def test_round_trip_3d_histogram_json(storage_type: bh.storage.Storage) -> None: """Regression test: 3D histograms with empty StrCategory axes round-trip correctly through JSON serialization. JSON serialization collapses empty array dimensions (e.g. shape (5, 0, 0) becomes (5, 0)), which must be handled correctly during deserialization. """ h = bh.Histogram( bh.axis.Variable([0, 1, 2, 3]), bh.axis.StrCategory([], growth=True), bh.axis.StrCategory([], growth=True), storage=storage_type, ) data = to_uhi(h) data = _json_round_trip(data) h2 = from_uhi(data) assert h.ndim == h2.ndim assert h == h2 def test_round_trip_3d_histogram_json_constructor() -> None: """Regression test: bh.Histogram(uhi_dict) works for 3D histograms after JSON round-trip (reproduces the exact example from the bug report).""" h = bh.Histogram( bh.axis.Variable([0, 1, 2, 3]), bh.axis.StrCategory([], growth=True), bh.axis.StrCategory([], growth=True), storage=bh.storage.Weight(), ) data = _json_round_trip(to_uhi(h)) h2 = bh.Histogram(data) assert h.ndim == h2.ndim assert h == h2 def test_from_uhi_malformed_weight_storage() -> None: """Test that malformed Weight storage ( missing required keys) raises ValueError.""" data = { "uhi_schema": 1, "axes": [ { "type": "regular", "lower": 0, "upper": 10, "bins": 5, "underflow": True, "overflow": True, "circular": False, } ], "storage": { "type": "weighted", "values": [1, 2, 3, 4, 5], }, "metadata": {}, } with pytest.raises(ValueError, match="Weighted storage missing required keys"): from_uhi(data) def test_from_uhi_malformed_mean_storage() -> None: """Test that malformed Mean storage (missing required keys) raises ValueError.""" data = { "uhi_schema": 1, "axes": [ { "type": "regular", "lower": 0, "upper": 10, "bins": 5, "underflow": True, "overflow": True, "circular": False, } ], "storage": { "type": "mean", "counts": [1, 2, 3, 4, 5], "values": [1, 2, 3, 4, 5], }, "metadata": {}, } with pytest.raises(ValueError, match="Mean storage missing required keys"): from_uhi(data) def test_from_uhi_malformed_weighted_mean_storage() -> None: """Test that malformed WeightedMean storage (missing required keys) raises ValueError.""" data = { "uhi_schema": 1, "axes": [ { "type": "regular", "lower": 0, "upper": 10, "bins": 5, "underflow": True, "overflow": True, "circular": False, } ], "storage": { "type": "weighted_mean", "sum_of_weights": [1, 2, 3, 4, 5], "values": [1, 2, 3, 4, 5], }, "metadata": {}, } with pytest.raises(ValueError, match="Weighted_mean storage missing required keys"): from_uhi(data) def test_from_uhi_structure_only_no_error() -> None: """Test that structure-only (no data keys) histograms load correctly.""" data = { "uhi_schema": 1, "axes": [ { "type": "regular", "lower": 0, "upper": 10, "bins": 5, "underflow": True, "overflow": True, "circular": False, } ], "storage": { "type": "double", }, "metadata": {}, } h = from_uhi(data) assert h.storage_type is bh.storage.Double assert np.asarray(h) == pytest.approx(np.zeros(5)) scikit-hep-boost-histogram-5f6f3e3/tests/test_storage.py000066400000000000000000000365671516627434300235670ustar00rootroot00000000000000from __future__ import annotations import math import numpy as np import pytest from numpy.testing import assert_array_equal from pytest import approx import boost_histogram as bh @pytest.mark.parametrize( "storage", [bh.storage.Int64, bh.storage.Double, bh.storage.AtomicInt64, bh.storage.Unlimited], ) def test_setting(storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) h[0] = 2 h[1] = 3 h[-1] = 5 assert h[0] == 2 assert h[1] == 3 assert h[9] == 5 assert_array_equal(h.view(), [2, 3, 0, 0, 0, 0, 0, 0, 0, 5]) def test_setting_weight(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Weight()) h.fill([0.3, 0.3, 0.4, 1.2]) assert h[0] == bh.accumulators.WeightedSum(3, 3) assert h[1] == bh.accumulators.WeightedSum(1, 1) h[0] = bh.accumulators.WeightedSum(value=2, variance=2) assert h[0] == bh.accumulators.WeightedSum(2, 2) a = h.view() assert a[0] == h[0] b = np.asarray(h) assert b["value"][0] == h[0].value assert b["variance"][0] == h[0].variance h[0] = bh.accumulators.WeightedSum(value=3, variance=1) assert h[0].value == 3 assert h[0].variance == 1 assert a[0] == h[0] assert b["value"][0] == h[0].value assert b["variance"][0] == h[0].variance assert b[0]["value"] == a[0]["value"] assert b[0]["variance"] == a[0]["variance"] assert b["value"][0] == a["value"][0] assert b["variance"][0] == a["variance"][0] assert_array_equal(a.view().value, b.view()["value"]) assert_array_equal(a.view().variance, b.view()["variance"]) def test_sum_weight(): h = bh.Histogram(bh.axis.Integer(0, 10), storage=bh.storage.Weight()) h.fill([1, 2, 3, 3, 3, 4, 5]) v = h.view().copy() res = np.sum(v) hres = h.sum() assert res.value == hres.value == 7 assert res.variance == hres.variance == 7 v2 = v + v h2 = h + h assert_array_equal(h2.view(), v2) def test_setting_profile(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Mean()) h.fill([0.3, 0.3, 0.4, 1.2, 1.6], sample=[1, 2, 3, 4, 4]) assert h[0] == bh.accumulators.Mean(count=3, value=2, variance=1) assert h[1] == bh.accumulators.Mean(count=2, value=4, variance=0) h[0] = bh.accumulators.Mean(count=12, value=11, variance=10) assert h[0] == bh.accumulators.Mean(count=12, value=11, variance=10) a = h.view() assert a[0] == h[0] b = np.asarray(h) assert b["value"][0] == h[0].value assert b["count"][0] == h[0].count assert b["_sum_of_deltas_squared"][0] == h[0]._sum_of_deltas_squared h[0] = bh.accumulators.Mean(count=6, value=3, variance=2) assert h[0].count == 6 assert h[0].value == 3 assert h[0].variance == 2 assert a[0] == h[0] assert b["value"][0] == h[0].value assert b["count"][0] == h[0].count assert b["_sum_of_deltas_squared"][0] == h[0]._sum_of_deltas_squared assert b[0]["value"] == a[0]["value"] assert b[0]["count"] == a[0]["count"] assert b[0]["_sum_of_deltas_squared"] == a[0]["_sum_of_deltas_squared"] assert b[0]["value"] == a["value"][0] assert b[0]["count"] == a["count"][0] assert b[0]["_sum_of_deltas_squared"] == a["_sum_of_deltas_squared"][0] assert_array_equal(a.view().value, b.view()["value"]) assert_array_equal(a.view().count, b.view()["count"]) assert_array_equal( a.view()._sum_of_deltas_squared, b.view()["_sum_of_deltas_squared"] ) def test_setting_weighted_profile(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.WeightedMean()) h.fill([0.3, 0.3, 0.4, 1.2, 1.6], sample=[1, 2, 3, 4, 4], weight=[1, 1, 1, 1, 2]) assert h[0] == bh.accumulators.WeightedMean( sum_of_weights=3, sum_of_weights_squared=3, value=2, variance=1 ) assert h[1] == bh.accumulators.WeightedMean( sum_of_weights=3, sum_of_weights_squared=5, value=4, variance=0 ) h[0] = bh.accumulators.WeightedMean( sum_of_weights=12, sum_of_weights_squared=15, value=11, variance=10 ) assert h[0] == bh.accumulators.WeightedMean( sum_of_weights=12, sum_of_weights_squared=15, value=11, variance=10 ) a = h.view() assert a[0] == h[0] b = np.asarray(h) assert b["value"][0] == h[0].value assert b["sum_of_weights"][0] == h[0].sum_of_weights assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared assert ( b["_sum_of_weighted_deltas_squared"][0] == h[0]._sum_of_weighted_deltas_squared ) h[0] = bh.accumulators.WeightedMean( sum_of_weights=6, sum_of_weights_squared=12, value=3, variance=2 ) assert a[0] == h[0] assert h[0].value == 3 assert h[0].variance == 2 assert h[0].sum_of_weights == 6 assert h[0].sum_of_weights_squared == 12 assert h[0]._sum_of_weighted_deltas_squared == 8 assert b["value"][0] == h[0].value assert b["sum_of_weights"][0] == h[0].sum_of_weights assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared assert ( b["_sum_of_weighted_deltas_squared"][0] == h[0]._sum_of_weighted_deltas_squared ) assert b[0]["value"] == a[0]["value"] assert b[0]["sum_of_weights"] == a[0]["sum_of_weights"] assert b[0]["sum_of_weights_squared"] == a[0]["sum_of_weights_squared"] assert ( b[0]["_sum_of_weighted_deltas_squared"] == a[0]["_sum_of_weighted_deltas_squared"] ) assert b[0]["value"] == a["value"][0] assert b[0]["sum_of_weights"] == a["sum_of_weights"][0] assert b[0]["sum_of_weights_squared"] == a["sum_of_weights_squared"][0] assert ( b[0]["_sum_of_weighted_deltas_squared"] == a["_sum_of_weighted_deltas_squared"][0] ) assert_array_equal(a.view().value, b.view()["value"]) assert_array_equal(a.view().sum_of_weights, b.view()["sum_of_weights"]) assert_array_equal( a.view().sum_of_weights_squared, b.view()["sum_of_weights_squared"] ) assert_array_equal( a.view()._sum_of_weighted_deltas_squared, b.view()["_sum_of_weighted_deltas_squared"], ) # Issue #486 def test_modify_weights_by_view(): bins = [0, 1, 2] hist = bh.Histogram(bh.axis.Variable(bins), storage=bh.storage.Weight()) yields = [3, 4] var = [0.1, 0.2] hist[...] = np.stack([yields, var], axis=-1) hist.view().value /= 2 assert hist.view().value[0] == pytest.approx(1.5) assert hist.view().value[1] == pytest.approx(2) # Issue #531 def test_summing_mean_storage(): np.random.seed(42) values = np.random.normal(loc=1.3, scale=0.1, size=1000) samples = np.random.normal(loc=1.3, scale=0.1, size=1000) h1 = bh.Histogram(bh.axis.Regular(20, -1, 3), storage=bh.storage.Mean()) h1.fill(values, sample=samples) h2 = bh.Histogram(bh.axis.Regular(1, -1, 3), storage=bh.storage.Mean()) h2.fill(values, sample=samples) s1 = h1.sum() s2 = h2.sum() assert s1.value == approx(s2.value) assert s1.count == approx(s2.count) assert s1.variance == approx(s2.variance) # Issue #531 def test_summing_weighted_mean_storage(): np.random.seed(42) values = np.random.normal(loc=1.3, scale=0.1, size=1000) samples = np.random.normal(loc=1.3, scale=0.1, size=1000) weights = np.random.uniform(0.1, 5, size=1000) h1 = bh.Histogram(bh.axis.Regular(20, -1, 3), storage=bh.storage.WeightedMean()) h1.fill(values, sample=samples, weight=weights) h2 = bh.Histogram(bh.axis.Regular(1, -1, 3), storage=bh.storage.WeightedMean()) h2.fill(values, sample=samples, weight=weights) s1 = h1.sum() s2 = h2.sum() assert s1.value == approx(s2.value) assert s1.sum_of_weights == approx(s2.sum_of_weights) assert s1.sum_of_weights_squared == approx(s2.sum_of_weights_squared) assert s1.variance == approx(s2.variance) # Raised on Gitter def test_UHI_variance_counts(): h = bh.Histogram( bh.axis.Regular(bins=1, start=0, stop=1), storage=bh.storage.WeightedMean() ) h.fill(0.5, sample=[1], weight=[0.5]) h.fill(0.5, sample=[2], weight=[0.4]) assert not math.isnan(h.variances()[0]) h = bh.Histogram( bh.axis.Regular(bins=1, start=0, stop=1), storage=bh.storage.WeightedMean() ) h.fill(0.5, sample=[1], weight=[0.5]) h.fill(0.5, sample=[1], weight=[0.5]) assert not math.isnan(h.variances()[0]) @pytest.mark.parametrize( "storage", [ bh.storage.Int64, bh.storage.Double, bh.storage.AtomicInt64, bh.storage.Unlimited, bh.storage.Weight, bh.storage.Mean, bh.storage.WeightedMean, ], ) def test_empty_axis_histogram(storage): h2d = bh.Histogram( bh.axis.Regular(10, 0, 10), bh.axis.StrCategory([], growth=True), storage=storage(), ) assert h2d.sum() == h2d.storage_type.accumulator() assert h2d.sum(flow=True) == h2d.storage_type.accumulator() # Issue #971 def test_non_uniform_rebin_with_weights(): # 1D h = bh.Histogram(bh.axis.Regular(20, 1, 5), storage=bh.storage.Weight()) h.fill([1.1, 2.2, 3.3, 4.4]) rslt = np.array( [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0), (0.0, 0.0), (1.0, 1.0)], dtype=[("value", "