pax_global_header00006660000000000000000000000064150136064250014514gustar00rootroot0000000000000052 comment=1dd8c3dfcc55235ae3753ce0db264da53bae4a80 numpy-groupies-0.11.3/000077500000000000000000000000001501360642500146015ustar00rootroot00000000000000numpy-groupies-0.11.3/.gitattributes000066400000000000000000000000501501360642500174670ustar00rootroot00000000000000numpy_groupies/_version.py export-subst numpy-groupies-0.11.3/.github/000077500000000000000000000000001501360642500161415ustar00rootroot00000000000000numpy-groupies-0.11.3/.github/workflows/000077500000000000000000000000001501360642500201765ustar00rootroot00000000000000numpy-groupies-0.11.3/.github/workflows/ci.yaml000066400000000000000000000035161501360642500214620ustar00rootroot00000000000000name: CI on: push: branches: - "master" pull_request: branches: - "*" schedule: - cron: "0 0 * * *" # Daily “At 00:00â€� workflow_dispatch: # allows you to trigger manually concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: Build (${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} defaults: run: shell: bash -l {0} strategy: fail-fast: false matrix: os: ["ubuntu-latest"] python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Set environment variables run: | echo "CONDA_ENV_FILE=ci/environment.yml" >> $GITHUB_ENV echo "PYTHON_VERSION=${{ matrix.python-version }}" >> $GITHUB_ENV - name: Setup micromamba uses: mamba-org/setup-micromamba@v1 with: environment-file: ${{ env.CONDA_ENV_FILE }} environment-name: npg-tests cache-environment: true cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{matrix.python-version}}-${{hashFiles(env.CONDA_ENV_FILE)}}" create-args: >- python=${{matrix.python-version}} conda # We only want to install this on one run, because otherwise we'll have # duplicate annotations. - name: Install error reporter if: ${{ matrix.os }} == 'ubuntu-latest' and ${{ matrix.python-version }} == '3.11' run: | python -m pip install pytest-github-actions-annotate-failures - name: Set up conda environment shell: bash -l {0} run: | python -m pip install -e .[dev] conda list - name: Run Tests shell: bash -l {0} run: | pytest numpy-groupies-0.11.3/.github/workflows/pypi-release.yaml000066400000000000000000000037301501360642500234640ustar00rootroot00000000000000name: Publish to PyPI on: workflow_dispatch: release: types: - published jobs: build-artifacts: runs-on: ubuntu-latest if: github.repository == 'ml31415/numpy-groupies' steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: actions/setup-python@v5 name: Install Python with: python-version: "3.11" - name: Build tarball and wheels run: | git clean -xdf git restore -SW . pipx run build - name: List contents of built dist run: | ls -ltrh ls -ltrh dist - name: Check built artifacts run: | pipx run twine check --strict dist/* pwd if [ -f dist/numpy_groupies-0.0.0.tar.gz ]; then echo "â�Œ INVALID VERSION NUMBER" exit 1 else echo "✅ Looks good" fi - name: Test artifact installation run: | python -m pip install --upgrade pip python -m pip install dist/*.tar.gz - uses: actions/upload-artifact@v4 with: name: release path: dist test-built-dist: needs: build-artifacts runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: release path: dist - name: Publish package to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.TESTPYPI_TOKEN }} repository-url: https://test.pypi.org/legacy/ skip-existing: true upload-to-pypi: needs: test-built-dist if: github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: release path: dist - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_TOKEN }} skip-existing: true numpy-groupies-0.11.3/.gitignore000066400000000000000000000006401501360642500165710ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Dev stuff .mr.developer.cfg .idea .project .pydevproject .settings/ .cache/ __pycache__/ .eggs/ .hypothesis/ *~ *.ini # Dynamic versioning numpy_groupies/_version.py numpy-groupies-0.11.3/.pre-commit-config.yaml000066400000000000000000000012601501360642500210610ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: debug-statements - id: detect-private-key - id: check-builtin-literals - id: check-case-conflict - id: check-executables-have-shebangs - id: check-json - id: check-merge-conflict - id: check-symlinks - id: check-toml - id: check-xml - id: check-yaml exclude: (.pre-commit-config\.yaml) - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.3.7 hooks: - id: ruff - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: - id: codespell numpy-groupies-0.11.3/LICENSE.txt000066400000000000000000000024371501360642500164320ustar00rootroot00000000000000Copyright (c) 2016, numpy-groupies developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 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 OWNER 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. numpy-groupies-0.11.3/README.md000066400000000000000000000363561501360642500160750ustar00rootroot00000000000000[![GitHub Workflow CI Status](https://img.shields.io/github/actions/workflow/status/ml31415/numpy-groupies/ci.yaml?branch=master&logo=github&style=flat)](https://github.com/ml31415/numpy-groupies/actions) [![PyPI](https://img.shields.io/pypi/v/numpy-groupies.svg?style=flat)](https://pypi.org/project/numpy-groupies/) [![Conda-forge](https://img.shields.io/conda/vn/conda-forge/numpy_groupies.svg?style=flat)](https://anaconda.org/conda-forge/numpy_groupies) ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fml31415%2Fnumpy-groupies%2Fmaster%2Fpyproject.toml) ![PyPI - Downloads](https://img.shields.io/pypi/dm/numpy-groupies) # numpy-groupies This package consists of a small library of optimised tools for doing things that can roughly be considered "group-indexing operations". The most prominent tool is `aggregate`, which is described in detail further down the page. ## Installation If you have `pip`, then simply: ``` pip install numpy_groupies ``` Note that `numpy_groupies` doesn't have any compulsory dependencies (even `numpy` is optional) so you should be able to install it fairly easily even without a package manager. If you just want one particular implementation of `aggregate` (e.g. `aggregate_numpy.py`), you can download that one file, and copy-paste the contents of `utils.py` into the top of that file (replacing the `from .utils import (...)` line). ## aggregate ![aggregate_diagram](/diagrams/aggregate.png) ```python import numpy as np import numpy_groupies as npg group_idx = np.array([ 3, 0, 0, 1, 0, 3, 5, 5, 0, 4]) a = np.array([13.2, 3.5, 3.5,-8.2, 3.0,13.4,99.2,-7.1, 0.0,53.7]) npg.aggregate(group_idx, a, func='sum', fill_value=0) # >>> array([10.0, -8.2, 0.0, 26.6, 53.7, 92.1]) ``` `aggregate` takes an array of values, and an array giving the group number for each of those values. It then returns the sum (or mean, or std, or any, ...etc.) of the values in each group. You have probably come across this idea before - see [Matlab's `accumarray` function](http://uk.mathworks.com/help/matlab/ref/accumarray.html?refresh=true), or [`pandas` groupby concept](http://pandas.pydata.org/pandas-docs/dev/groupby.html), or [MapReduce paradigm](http://en.wikipedia.org/wiki/MapReduce), or simply the [basic histogram](https://en.wikipedia.org/wiki/Histogram). A couple of implemented functions do not reduce the data, instead it calculates values cumulatively while iterating over the data or permutates them. The output size matches the input size. ```python group_idx = np.array([4, 3, 3, 4, 4, 1, 1, 1, 7, 8, 7, 4, 3, 3, 1, 1]) a = np.array([3, 4, 1, 3, 9, 9, 6, 7, 7, 0, 8, 2, 1, 8, 9, 8]) npg.aggregate(group_idx, a, func='cumsum') # >>> array([3, 4, 5, 6,15, 9,15,22, 7, 0,15,17, 6,14,31,39]) ``` ### Inputs The function accepts various different combinations of inputs, producing various different shapes of output. We give a brief description of the general meaning of the inputs and then go over the different combinations in more detail: * `group_idx` - array of non-negative integers to be used as the "labels" with which to group the values in `a`. * `a` - array of values to be aggregated. * `func='sum'` - the function to use for aggregation. See the section below for more details. * `size=None` - the shape of the output array. If `None`, the maximum value in `group_idx` will set the size of the output. * `fill_value=0` - value to use for output groups that do not appear anywhere in the `group_idx` input array. * `order='C'` - for multidimensional output, this controls the layout in memory, can be `'F'` for fortran-style. * `dtype=None` - the`dtype` of the output. `None` means choose a sensible type for the given `a`, `func`, and `fill_value`. * `axis=None` - explained below. * `ddof=0` - passed through into calculations of variance and standard deviation (see section on functions). ![aggregate_dims_diagram](/diagrams/aggregate_dims.png) * Form 1 is the simplest, taking `group_idx` and `a` of matching 1D lengths, and producing a 1D output. * Form 2 is similar to Form 1, but takes a scalar `a`, which is broadcast out to the length of `group_idx`. Note that this is generally not that useful. * Form 3 is more complicated. `group_idx` is the same length as the `a.shape[axis]`. The groups are broadcast out along the other axis/axes of `a`, thus the output is of shape `n_groups x a.shape[0] x ... x a.shape[axis-1] x a.shape[axis+1] x ... a.shape[-1]`, i.e. the output has two or more dimensions. * Form 4 also produces output with two or more dimensions, but for very different reasons to Form 3. Here `a` is 1D and `group_idx` is exactly `2D`, whereas in Form 3 `a` is `ND`, `group_idx` is `1D`, and we provide a value for `axis`. The length of `a` must match `group_idx.shape[1]`, the value of `group_idx.shape[0]` determines the number of dimensions in the output, i.e. `group_idx[:,99]` gives the `(x,y,z)` group indices for the `a[99]`. * Form 5 is the same as Form 4 but with scalar `a`. As with Form 2, this is rarely that helpful. **Note on performance.** The `order` of the output is unlikely to affect performance of `aggregate` (although it may affect your downstream usage of that output), however the order of multidimensional `a` or `group_idx` can affect performance: in Form 4 it is best if columns are contiguous in memory within `group_idx`, i.e. `group_idx[:, 99]` corresponds to a contiguous chunk of memory; in Form 3 it's best if all the data in `a` for `group_idx[i]` is contiguous, e.g. if `axis=1` then we want `a[:, 55]` to be contiguous. ### Available functions By default, `aggregate` assumes you want to sum the values within each group, however you can specify another function using the `func` kwarg. This `func` can be any custom callable, however you will likely want one of the following optimized functions. Note that not all functions might be provided by all implementations. * `'sum'` - sum of items within each group (see example above). * `'prod'` - product of items within each group * `'mean'` - mean of items within each group * `'var'`- variance of items within each group. Use `ddof` kwarg for degrees of freedom. The divisor used in calculations is `N - ddof`, where `N` represents the number of elements. By default `ddof` is zero. * `'std'` - standard deviation of items within each group. Use `ddof` kwarg for degrees of freedom (see `var` above). * `'min'` - minimum value of items within each group. * `'max'` - maximum value of items within each group. * `'first'` - first item in `a` from each group. * `'last'` - last item in `a` from each group. * `'argmax'` - the index in `a` of the maximum value in each group. * `'argmin'` - the index in `a` of the minimum value in each group. The above functions also have a `nan`-form, which skip the `nan` values instead of propagating them to the result of the calculation: * `'nansum'`, `'nanprod'`, `'nanmean'`, `'nanvar'`, `'nanstd'`, `'nanmin'`, `'nanmax'`, `'nanfirst'`, `'nanlast'`, `'nanargmax'`, `'nanargmin'` The following functions are slightly different in that they always return boolean values. Their treatment of nans is also different from above: * `'all'` - `True` if all items within a group are truethy. Note that `np.all(nan)` is `True`, i.e. `nan` is actually truethy. * `'any'` - `True` if any items within a group are truethy. * `'allnan'` - `True` if all items within a group are `nan`. * `'anynan'` - `True` if any items within a group are `nan`. The following functions don't reduce the data, but instead produce an output matching the size of the input: * `'cumsum'` - cumulative sum of items within each group. * `'cumprod'` - cumulative product of items within each group. (numba only) * `'cummin'` - cumulative minimum of items within each group. (numba only) * `'cummax'` - cumulative maximum of items within each group. (numba only) * `'sort'` - sort the items within each group in ascending order, use reverse=True to invert the order. Finally, there are three functions which don't reduce each group to a single value, instead they return the full set of items within the group: * `'array'` - simply returns the grouped items, using the same order as appeared in `a`. (numpy only) ### Examples Compute sums of consecutive integers, and then compute products of those consecutive integers. ```python group_idx = np.arange(5).repeat(3) # group_idx: array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]) a = np.arange(group_idx.size) # a: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) x = npg.aggregate(group_idx, a) # sum is default # x: array([ 3, 12, 21, 30, 39]) x = npg.aggregate(group_idx, a, 'prod') # x: array([ 0, 60, 336, 990, 2184]) ``` Get variance ignoring nans, setting all-nan groups to `nan`. ```python x = npg.aggregate(group_idx, a, func='nanvar', fill_value=nan) ``` Count the number of elements in each group. Note that this is equivalent to doing `np.bincount(group_idx)`, indeed that is how the numpy implementation does it. ```python x = npg.aggregate(group_idx, 1) ``` Sum 1000 values into a three-dimensional cube of size 15x15x15. Note that in this example all three dimensions have the same size, but that doesn't have to be the case. ```python group_idx = np.random.randint(0, 15, size=(3, 1000)) a = np.random.random(group_idx.shape[1]) x = npg.aggregate(group_idx, a, func="sum", size=(15,15,15), order="F") # x.shape: (15, 15, 15) # np.isfortran(x): True ``` Use a custom function to generate some strings. ```python group_idx = np.array([1, 0, 1, 4, 1]) a = np.array([12.0, 3.2, -15, 88, 12.9]) x = npg.aggregate(group_idx, a, func=lambda g: ' or maybe '.join(str(gg) for gg in g), fill_value='') # x: ['3.2', '12.0 or maybe -15.0 or maybe 12.9', '', '', '88.0'] ``` Use the `axis` arg in order to do a sum-aggregation on three rows simultaneously. ```python a = np.array([[99, 2, 11, 14, 20], [33, 76, 12, 100, 71], [67, 10, -8, 1, 9]]) group_idx = np.array([[3, 3, 7, 0, 0]]) x = npg.aggregate(group_idx, a, axis=1) # x : [[ 34, 0, 0, 101, 0, 0, 0, 11], # [171, 0, 0, 109, 0, 0, 0, 12], # [ 10, 0, 0, 77, 0, 0, 0, -8]] ``` ### Multiple implementations There are multiple implementations of `aggregate` provided. If you use `from numpy_groupies import aggregate`, the best available implementation will automatically be selected. Otherwise you can pick a specific version directly like `from numpy_groupies import aggregate_nb as aggregate` or by importing aggregate from the implementing module `from numpy_groupies.aggregate_weave import aggregate`. Currently the following implementations exist: * **numpy** - This is the default implementation. It uses plain `numpy`, mainly relying on `np.bincount` and basic indexing magic. It comes without other dependencies except `numpy` and shows reasonable performance for the occasional usage. * **numba** - This is the most performant implementation, based on jit compilation provided by numba and LLVM. * **pure python** - This implementation has no dependencies and uses only the standard library. It's horribly slow and should only be used, if there is no numpy available. * **numpy ufunc** - *Only for benchmarking.* This implementation uses the `.at` method of numpy's `ufunc`s (e.g. `add.at`), which would appear to be designed for performing exactly the same calculation that `aggregate` executes, however the numpy implementation is rather incomplete. * **pandas** - *Only for reference.* The pandas' `groupby` concept is the same as the task performed by `aggregate`. However, `pandas` is not actually faster than the default `numpy` implementation. Also, note that there may be room for improvement in the way that `pandas` is utilized here. Most notably, when computing multiple aggregations of the same data (e.g. `'min'` and `'max'`) pandas could potentially be used more efficiently. All implementations have the same calling syntax and produce the same outputs, to within some floating-point error. However some implementations only support a subset of the valid inputs and will sometimes throw `NotImplementedError`. ### Benchmarks Scripts for testing and benchmarking are included in this repository. For benchmarking, run `python -m numpy_groupies.benchmarks.generic` from the root of this repository. Below we are using `500,000` indices uniformly picked from `[0, 1000)`. The values of `a` are uniformly picked from the interval `[0,1)`, with anything less than `0.2` then set to 0 (in order to serve as falsy values in boolean operations). For `nan-` operations another 20% of the values are set to nan, leaving the remainder on the interval `[0.2,0.8)`. The benchmarking results are given in ms for an i7-7560U running at 2.40GHz: | function | ufunc | numpy | numba | pandas | |-----------|---------|---------|---------|---------| | sum | 1.950 | 1.728 | 0.708 | 11.832 | | prod | 2.279 | 2.349 | 0.709 | 11.649 | | min | 2.472 | 2.489 | 0.716 | 11.686 | | max | 2.457 | 2.480 | 0.745 | 11.598 | | len | 1.481 | 1.270 | 0.635 | 10.932 | | all | 37.186 | 3.054 | 0.892 | 12.587 | | any | 35.278 | 5.157 | 0.890 | 12.845 | | anynan | 5.783 | 2.126 | 0.762 | 144.740 | | allnan | 7.971 | 4.367 | 0.774 | 144.507 | | mean | ---- | 2.500 | 0.825 | 13.284 | | std | ---- | 4.528 | 0.965 | 12.193 | | var | ---- | 4.269 | 0.969 | 12.657 | | first | ---- | 1.847 | 0.811 | 11.584 | | last | ---- | 1.309 | 0.581 | 11.842 | | argmax | ---- | 3.504 | 1.411 | 293.640 | | argmin | ---- | 6.996 | 1.347 | 290.977 | | nansum | ---- | 5.388 | 1.569 | 15.239 | | nanprod | ---- | 5.707 | 1.546 | 15.004 | | nanmin | ---- | 5.831 | 1.700 | 14.292 | | nanmax | ---- | 5.847 | 1.731 | 14.927 | | nanlen | ---- | 3.170 | 1.529 | 14.529 | | nanall | ---- | 6.499 | 1.640 | 15.931 | | nanany | ---- | 8.041 | 1.656 | 15.839 | | nanmean | ---- | 5.636 | 1.583 | 15.185 | | nanvar | ---- | 7.514 | 1.682 | 15.643 | | nanstd | ---- | 7.292 | 1.666 | 15.104 | | nanfirst | ---- | 5.318 | 2.096 | 14.432 | | nanlast | ---- | 4.943 | 1.473 | 14.637 | | nanargmin | ---- | 7.977 | 1.779 | 298.911 | | nanargmax | ---- | 5.869 | 1.802 | 301.022 | | cumsum | ---- | 71.713 | 1.119 | 8.864 | | cumprod | ---- | ---- | 1.123 | 12.100 | | cummax | ---- | ---- | 1.062 | 12.133 | | cummin | ---- | ---- | 0.973 | 11.908 | | arbitrary | ---- | 147.853 | 46.690 | 129.779 | | sort | ---- | 167.699 | ---- | ---- | _Linux(x86_64), Python 3.10.12, Numpy 1.25.2, Numba 0.58.0, Pandas 2.0.2_ ## Development This project was started by @ml31415 and the `numba` and `weave` implementations are by him. The pure python and `numpy` implementations were written by @d1manson. The authors hope that `numpy`'s `ufunc.at` methods or some other implementation of `aggregate` within `numpy` or `scipy` will eventually be fast enough, to make this package redundant. Numpy 1.25 actually contained major [improvements on ufunc speed](https://numpy.org/doc/stable/release/1.25.0-notes.html), which reduced the speed gap between numpy and the numba implementation a lot. numpy-groupies-0.11.3/ci/000077500000000000000000000000001501360642500151745ustar00rootroot00000000000000numpy-groupies-0.11.3/ci/environment.yml000066400000000000000000000002041501360642500202570ustar00rootroot00000000000000name: npg-tests channels: - conda-forge - nodefaults dependencies: - numpy - pandas - numba - pytest - numpy_groupies numpy-groupies-0.11.3/conftest.py000066400000000000000000000013711501360642500170020ustar00rootroot00000000000000""" pytest configuration to silently discard test items with invalid parameter combinations See: https://github.com/pytest-dev/pytest/issues/3730#issuecomment-567142496 """ def pytest_configure(config): config.addinivalue_line( "markers", "deselect_if(func): function to deselect tests from parametrization" ) def pytest_collection_modifyitems(config, items): removed = [] kept = [] for item in items: m = item.get_closest_marker("deselect_if") if m: func = m.kwargs["func"] if func(**item.callspec.params): removed.append(item) continue kept.append(item) if removed: config.hook.pytest_deselected(items=removed) items[:] = kept numpy-groupies-0.11.3/diagrams/000077500000000000000000000000001501360642500163705ustar00rootroot00000000000000numpy-groupies-0.11.3/diagrams/aggregate.png000066400000000000000000000220561501360642500210310ustar00rootroot00000000000000‰PNG  IHDR ‹¡œ’¨sRGB®ÎégAMA±� üa pHYsÃÃÇo¨d#ÃIDATx^íÝoLgžð۾خ6Í©·ÊžÔùƒ +6RÏ—”na•¶ 4‹YusK‚Ô,F!%§˜¨Ió¼ŠJè¦iW�HAÁ¡Â4‘HXÔËÝÓ’+ÑáíÑ´ÖI):° áEOÛtU)tOÛ·ë{ž™gÆãì±±ÍLù~4Jæ™™Ç ÃØóóóü晼h4J ññ?(!è!BÐC„ ‡ @€îö�÷Í7ÔÞN7oòùu먧GZ + "¤xgÎÐè(;ÆçY„Ä&XyÐËï›oè¹çèÅù„ð`¥ú–FH¥”—GaQHÚ�ôä! Qž“üÔU!¬§NÅþ…d¾œ›sVðèO?s�iÑ'¬ô]+ïÏsV°úûsVð݇V:¶ÖòÅ÷Öˆ9‹ØðøÄœ|gM�˜Ó@€Þ"Ïãqò¯“'ôä•RX^(ͨÙªF1Ï…©4�J;DI.òêò¤ÝrþâlM•Æ!¾<Ü{iAÌPcl•n­¼J]‹ÒOç;,Oñ¿€Æ"’{Œ¢~>Óà§h”¢cd#ª¬& РY ‘—ýë�"áA¶žª+¥ÂåÙ‰Ú¥êÒÔàM—° ìÔ"mìoàÁYi)Ù'”êDNMuQ�.«æ¯]{ñ<* IÛ‡¨$@ö„¬`©ô²Ù*©„¨P‡®+³%ו)4Á—T²`ЍÑÉÂ+sK+$],ä P­Ú´”䬧Уü•)Ú%•‰Ž¶ÇÅg,†Ó¾¸ní‚Ø‹»¥}cá^ ©4±€FJyH6ª.¡ ƒ$S÷©¡…/¹?%–\÷RI5om’›—vÈ‹¼¥G­¾€²‹9¡¤HÌ0¶²¨Rÿâvé•§FT5Ô˜ÇÉ�”‹ w”&ß(§†[Ö¬ÎW§#Ãb±y=è¬~zµ]žŽ™~w­µ·ÌÈ¡‚§Vlïœe³Ð±µÖ»lÔó¤Ý®�Ê|&>¬µ·Œå>dw~ýØ ûá¯?åìJ1S›w´ÉM/aêP‘�/ ôKÝ[RT$ºØrBôHžìMb!XTùé‡�"b¨y¯ÊÜßìÃ¥ôDáåG¡ÏÙ4°ûJ•©?b¬µ·4ìa±Ñà�šŸeS³Ö±µÖ»ŒÛ= Í)Óˆk­XlRÖÙ[‹}&¨fÏÿöš˜Í‰#$¹£�w«…( u¨ñ%V¢ðT¬‹-óˆE!%)Ô.–CEW”³ò åûZ‹é½Š¢éLû�¾:кU.–·^vÑ•³¾rÑl,¶·½ÛwRÿ×Sç~&˜šµŽm<³¿Ë {¬zÞF¼ÇßÛ5Ø·K”³/ÅIîhó^çIH¢CÍF¥˜i°_YÂHjl³8 v½¥G~©åÇåˆè¿Ò*åãvË]V¼ÇJt^‰å2u™Dí×Òõsi‹Êëijj^oQ‹ü �Ø/`o ˆeÊÂØO˜·`yl.\/æL'ù„vo/%¢Ñ›>¢qÿ°9G‡²ÖÞn¨½ùuk™(˜žµŽíLü.ƒì±èy{÷mOðùÖ½¹üò” B²óæ"5ÇHÅ;ÚîÓõû´QI ÚÑ@ÞÓ¼ÓMÛÅÆS§½âŽ}.L¥Rîvfš6¤}‹E`C9èecñ‡“ß»·¨ûS�jàhª• mh‚Æë4zx�šš�&ûB!O xܳà/PÑõó1u�ùV%í¡1‘Öž{3�G<ã5¯0kõƒÐ$ço� Ó¾WV_° 4o¢`8"/2kí­µXùØšý]&\­²JZg‘½µæyûÁ‘}—6ŸìmÈé› B²Qo;»nêG’»Õ¼Ú¡Ä:;ø}dr§›Êææãˆá”Ø$ßù¯Ü˜¶T6 Q‰t?ŸNSˆźì²�üRVTŠ)xQFô÷{Y`�†Úù®&B,ª:-Å$J5±¦émƒÁލ'ÿ0¯zÏà"lî±ØÑYxO]JŒÔ!â£Þå�”4ÒçN|ú«�ÓÛÄRó™ž Š¹aÏÓŽHÓ£þ}ùb� Yko­ÅŠÇÖ*ï2æ…V5§gn¤™ÚÊžôŒŠU&d¡½µây;Ó{ú2í{­6Ç;š°—�G9r¢�4’À¢ia¬1¨bÞ62y¹2ioΟ��À4ï§ÄU‘^-váVvƒO¬¢TT×VtñåêòbüîÉ¿Z a€­`#û×ë”: DkLƒ$r�É�…ˆTº*(4Á�JÚ�Šíln>Êon2ÒŠÔà n;R¨µ0ýžèUtñ˜)ÐÔÄ~¯ø¸/‡bi¤ïRUþ·Y3$6:Ø¿Ó�ÕOŸÍ{¤ôå“ÃfÊOkí­µXñØZå]¦³ÖÕÕì «#·DÙÜL¾·Ö;ogÏÿÓ›¿ßs颲«9“jÒÊ%ÂUI{H×cXR�³<õ¬ZJ$–!/¾3PC¾ ]æì_g/¤ñ¶=“?v­“ñ~}“²ÖÞZ‹µ�­¹ße › [`ø•Y÷Örçí�wßsœ;¸Es’ARzŽÔ‰&ÌogÙX _b/ŠïU}]óC©¡XÓ|rfÐÒ0±'ýò˜šáŽZÝ�“v€ýzÚŒ¤eWh=åf“Ÿ¿‰èÕŸ¿ Šì |ã*;Ë͹»ÖÚ[kù[Ó¾Ëô"‘ 9œÛMž7¥0÷ÞZì¼ýÀ�(xø'…ÒHH…�Õ°âµÊœŒŠ„Irý:ž?›nÞ¤¿ù±p¾@“]ô²I’¦\+½jJçœ5´H}qR·�X%…@zJ-y¥\+åV6k‰Úbm•Õr°&ýºpL$¡óŸ d$-5/|i¦}Ϊ¾Í¿ÙgÖ$‰ ®&]©òÜ‘‹Ãž=>zõuåÛ˜ÙXko­ÅÊÇÖìï²8_uÕUª;è²Dß°é÷ÖbçíÏÞ™üËš‰ßí¿k�Ï¿‘íûÚ!IFGÅôßÿMÿ÷ba9tˆg �7Š©x+”’]¤yE¶xÁkAS+]6÷˜fGüê�cá‘0)YOb·X€•Ó é¶;6ÔïÇà/‚¿™ï²Ùں캺G‘¶jòT0ôVìÖYÓ±ÔÞÎ^ÜÆGÓ~ê™¶�‰>>VÎçWyFÄZÓ±Ô±µÖ»ŒÅò}alrø�ÁP¨#Öèa:ÖÚ[‹}‚-Ÿ¼h4*f�9u*ö¯ÖP#oÊa‘ŠÚ¹6Iƈ ¥Áoöa¿œ³R ã?ý@ÌA¦EŸ°Òw­¼?ÿUÌYÁêïÿQÌYÁwZéØZËß[#æ,bÃãsVð�5 <Ù mH©Ðö²I}_I»¾2J 帠eíø¶A„dŒ¾·ŒÉ@טzÙâ-ÖË A/ÈÐË–=èezÙ² ½l† BÐ[r„ÔQÊŸŒfŽ2mHzKŽ�øgSzìÒ ñV+ÜÝn!òãÄË}3¢,Qž1.OG†Å⤴£Þ9;³ùÜ£;ni851)ãÏ&_‹MÕ—r’Ïn­½Õ9$�)O‡îŠ¥IÜ=¬VYµëb6w;½cË<è¬V+3|Ч'£ï2ATo¹-Ê2êQW,óÅ¿…¬ZÜ-�¨’R­¥u+?Q;¹ >Î_þ5«}QΦôNÂÜœºw~-$¢L?õ*¾øUû ½éîî×TQ'cu†6$Ȫ�°OضÖbQމ=c<òp æ½*Cß3�¿¬™ðü§RëÞ‰­)è·µ#ôù#1IãϾ4ººÕŠŸ?êß—“§YkoµXxTýYó'_O}ŧîÚžzAÒìÅmõo KU¦úë‚'ŸÉâ¸Ûé[v�)=QxY®8°ûJUÖ®4~—)f»Îö‰ÙÌácO×Ó@(4Ǧ‘fj+Scš«à�ø˜lʵºw�·• S–ä…ù'ªS÷n"GaòwH¡ÕHþþxýìKï$ÌÝ©ËÈÏÓ4(ƒ¼o}C]ø—¾]—jŒ:[.ªU”Šì�R´^¬NÃ’#¤¡FÞ¢£6èðb#ŸiÌãËùTJÚ$¥6ÐTgxbÓü*Ê6|­“Ïx�ñ/¦Rõ¥×‡å7ÛU¾—";^ S¾�}¸¿wãCQ\Üú¿{8ì'¼áZ°Õݼ‰®ÞÌÞÇDFYjoïöPíñZåSlKeÑg3q-! X»ÿöÔMå‘eMÍÏÒµÁœü¾F�í´ïЉà«­[åbyëe]9ë{ 3(óï2ÙLçÏxMß@�(gÆèù¶`]wëËri­««Ù1ÞÖ}‹¬J$ßÕ?ׯ<í…zyôŒä"DŠ7Ûyá*í>˜ì!.³�ÕõÔškMöÇÊŒôNœ�º†mÝ{n3]òl\VÍžÿí5ÚsàÐrFH ðò0¥(Ä{ߢ!* �]²H�_Ú Jí%䌒à�z~>Ó W#¹¯ÑNÔ.^0TD¥ÒR0L?^wÒ§ò±¶q8òŽá§ýl.LëDžx�ìbš1›%ž›)Xio'Bj[Álø3¢¯Oã\мH¶8¶‘È'´{{ìì½é#÷g¼70Kï²/œø4óϵ��a]­üõ…D“aö—K°*U“3¹è½Òí>tœ©Oú$¶µúsù¼¶ôNœ�º©y¾ ÅgÞy÷ð=ǹƒ[D1-Ùéeó«™I6âO·÷ê ö©ÃQ»[ø¿§—Ó ±Ÿ@•ážlns‹yHS Éž‘ ÉùÛêk)?Ds&4NT´n ߌš½ÔØö‰ë Ñ(_½Ò…¿,i=ÖÚÛ-‡Þrð'ÔJ=k#‡ÊOŽ;ÞlJñS-þ˜� öTO 4=¶B“Tœ/¶šö½²ú‚m yù¾v«Rz—Ývï}¯Øs!å·dêÖåÏï¬ZÔƒÉ QáúÜ~9��Îí98ÿR’ÞI˜ëS÷Z¥’0KBŠñ;|oW‹ÚgˆÔ€´¹¢ri—‡lDH%dsœ½ˆÿ;¥½Ø–P¥6³»‚X˜¥tH¯àu"};}]ÑùÁÿ�‰�¼.k”4ÒçN|ú«�Ó©~[åâô­îE9”ݲSã»/ûî§ÍYû �rd³ ?žµöVµþÀµ¯þ«ùÙžú§VÉ I×öŒí„»‡w^£âæC†RÒ�ê±�æWjɰçiG¤éQÿ¾ejÒKã]6íû­�~õºÒ��Ak_Úé ž j†òlçÑ6ö5‡K°Ê°[žú2Ò–“Q³ÿþ~�ê*=}æ‘ÞI˜ËSW›lôÑIò¼üØMWš’¬]à îë{ãgb©13öߣ}¯Õ.q׿-™Ú]Ro�HNBR:4ýlö¦€X˜]±4Òw©*�;…Œ"ž²í£Í¿y§1µ‹iŠbÄíIýžŽuÞ>ULWnä(-ÂÔ{;Ý»=vëYÁSÛzÕÎÑ™Î]O=ÓVôþÔW_¿Im›VNaÏyÊv/9Þ¼¤f2eEªÇvC!ÏÄ�î¬~úlþØ#%¥c9º>S~—Ív5¶Þs½k¼ç.kô�œ¡6‡¸ùë(lVЬ2„§l_e‘òÛmùší¬–÷Gžªçß>{«»mÜÑ|(·Q™!é�„ËuꮯímuÐå‘DY?]¢šÂ¸à)™.¼ùûÍ'�*;Ÿ¶oK„ÄÈã„Ú¥<'I) w”æ9½¢°^|g †|ºŒuò°ðˆ}!&×»þôKIWœ˜Éu’£ ÷vCíMq·š4ÝVšéÞýÇ‚µïO�ãdžÝ_G½; Þ˜ÆÂ£ò“ãTû~ªÍNK`ìØ®³Òx۞ɃŸ»ÄýM<½cY{— _òŒgµi–çâ(·€õ È8ÚÄû8Áª$Xxäh ÒîîWf?´»$í•îåg}g¯š²‰Iï$\æSwjj�ÄÒ-ûvÑåÎósNgzO_Î@³,R€ãîUãYD ;D©`#ß@Û¿3u_ÌasóÆ$ºw7$šà�F þ¸^¶Ü+´¹Ì ·ÈáÑÃŽlö¯-¬h}ò»yã<�Ó¦|)ÅZb‰½å)DŽBÍGYþ�ŒÞ-%-±ðHŽ®r+é±ÍÏßDôêÏc- wn\¥bgyÎ"¹Å$y—ݾÑGô©Ç!†PZSÅŠ}5|>Ó£"q<ƒ§¸¹^Ü¿'ÁªyF=rx4ךë–Þ€D»+M؀ĥw.Û©š &Ì*(0ÖNÌ�h׎%7 1ËÔ†Ôd�=¨¤ÑÉ3“Ž*‰ÛR¨¤&nw”’¾ËÇÎ6§ûS¢Ä„;4�FaêgÛo÷¸¥áÑ#1³‚Ø‹xHäu渗M5ísVõénœ™éü%ÿ€Ö�€'m¹ áуÎê=>Ç)·ö“pöR9ÏMyeÑa+ÙõWhwSîZº+ímyy-OîS;Ýîž?¤ºò2Qdf/nãsÛã÷|¦sWuÏ2„GF�íW“‹®T)#' {öøèÕו/åËÁØ»l[‡Ü%§Lünÿš>>Ÿr¦`2£n{ÙqjîZ ÕgáU|À$û¼�g}eõW—%<’� ãÞÛœJ~šçÔ�xwW.Öð3ÓûÓškÏ·îÕ¦"±íy–Re¯þðJ Hº�Ó¶,R …BÔoÃyYQ¹iŸ«à=e�&±v¢Eê8Ó²Q¯º�4o7º/¶Ï³S �¢]bÛT¹\ôÑGâ¥^ÊÍ�f`s÷jš�JÚýjDŸ¿«·zƉÆ[ŸãóbÌ:íÐØkƒ¿Ft]fÓ“ŸŠ¹)C‚ýïÛ«›ÆHÁÆŒwxñ©ôŸ�cúÑy‹Ë<âGšÊþíG>�uäg•µöVk˹¯»kÇÛ6‰¥úÞºî¯Îkïe›�œ¿çRßû¿w§’ØÄ§, ™Þ±å©KÒð’RŪÉSÁÐ[Y æ2ý.Ë.7ðIHÊ(á*Y$¢äÇ( ÝW땺|ÊÉ ‘¢éõEúõÚ[õw,;ÎVEÒU÷6ÙI¸¼§®ˆrät쩘úbò¢òùóÁ±œO?ªþH3˜¤„78-Dn@JñÆ·EåE£Q1›C�ä¼�U}9·Ì#d¤äÑŸ~ æ Ó¢OX)£1ïÏsV°úûsVð݇V:¶ÖòÅ÷Öˆ9‹ØðøÄœ|g�2`�Æ·(S C!è!BÐËy„TÑ{˜€)å#À´!­lá©û±ë OG:]äo/¡ÀDH^™ÃƒQÚX`‰Î¹m¦ÅÜ=AC�NoI{¯eº«¼N9 IH�ie“®Ú’¡Æ<ûDKtÌmòf«â—s2}{‡’Pl×v½š“”€dö N¥Íñ µS“Ý:™ð+"¤•Í^Ä3žCRãQ(Ö�eúî‹áùS<>2}{GìBî'§™ïÓåÏYŠÍÝk¥”4€• ÒÊf+ØH�&§Ôx$.Þ±f%ÈéfÁ5ø-uû’¹sö¿ H–JIX‘!­pR#’>U»¤ºÒB×rsj”Ã#kf˜›4mJ¾Ÿ É.w æå9YQÎò±Fßÿ‚7€é!BZálî–vqQ.,R¶Œe’;LOî]³bx$í¹i[i´Y=¿Û_mÈZœx“˜"¤�]m¤á%Ò‡&¾Ê°«‹´Ÿ¼]FmD0k»�”+Ãþ�ÝÃÄ™¶•#nØg{u£fŒrÞrÒ¡µf“"À “Ǿv‰Y�  @€"$=DHzÖÊÔ¢<'5ø)Ã÷�dée³(ü?sbÎ ¢OX)ÿ»Ç­tïÂ7Ñ/Åœüí7ÿ'æ¬àç¬tÞ®²áÆC€ C€^Ž#¤0•æQž:5ŠÅ²ŽRÊ+e›Ä 5òÍäñcøZ'Ÿñ:¥ºÊ–|éu5¯¬r&½—€,—ÒåÙ‰Ú)Sƒ7…ˆÄ=FQ?ŸiðKÕÇ(Ö¨Ì^'�H^¥örÆI $zYX¡r!5:YBcnQdºXh ÚQ\ŠöP,‹ÈÝÂÿ=�‰—M…vØ\³Œœ|Çmzµý•ÎYQV<è¬fËå騰X˜Tzµ–näPÁS«”éÐ]±4‰»‡Õ*«v]œK³$ÒYö¤Â}K,L*½Z©»íÎ_³ZLÎyçMûœÊÚ5îŤҫ•Ô�_?öÃBíôS¯²Ãñ«öß‹º»_SE�ŒÕ5fÔó¤Ý.Oe¾¸c{Ë#–Ï_•„üšÕ¾ˆ(ÀòÈY„4D^þ„TQ*XÈD� QJ_ Å=2S/›‚¡FùA*¯³ty‹>ìaqÌÍÂæM¢Ã�Ò…—…>gÓÀî+U†Â�ôj- �ª?kþä멯øÔ]ÛSo H𽏭~â­a©ÊT]ðä3ž±*óX ã8þã�9É@]O•¡p'½Z©›éüeÍ„ç?E²i æÞ‰­G47¶v�cðAi-›:^+J¯–a»¿˜ü‹2ýGÃZ±xëê¿ôíºTc$ÐÙrQ­¢T$r­«—(â«~²žB¡96�4S[™ ±UU“ÍAyU÷îñ¶2÷¨¼&±Q7‹�FòÏ8D–2µ3BzÖ8•´‡øÃ3%üYšËgÚ÷J±hæ­Ÿ‰1Ó¾C'‚¯´n•‹å­—]tå¬ï�\\Lzµ2àî`Õ¯U.i[*ëˆ>›™ÅŬÝ{êæqi-kj~–® f)ž‹t6¯èxY.¾Ü1PG=g;“|ýO¯V:ÖøÝÃa—8~åûZ‹é½J“ÏpËs'¨5ø»Æ b�!éÕʬ­{Ïm¦K~ƒí‰ªÙó¿½F{ÊL„4z¾-X×Ý*þ†k]]ÍŽñ¶n9ÎÍwõÏõ»ò¥yz¡žE<=#IC¤ÙÎêzê͵¾$À²B„”A�‰�˜ãσ�ý�oJ¯[¬(Í•vtÈÏ -í½t™h{Úàú×GJ4£‰|B»·—‹û˜¿é#÷'î‡J¯V†L„Ô.ŠÙðgD?^ŸÆNó"™§ºJq•dn±�ŽÆß¿™8ØI¯V†L<�#ÌÛ7úÈu0Õ@'½ZÙð|�Ò¼dÐ�wßsœ;¸E—hv†�‹Zùë ‰&Ë�g“3Éþºkô‡:^Xv9‹�¤ž/ïuQâ»Þ 6ò´¤X�A4u_Ì$ Á¸{Õ2ô²ÆUì�šŒä‡¸§âššxëŸqŠ^º@Ü/“aB“Tœ/.oÓ¾WV_° 4o¢`8á§wzµ2aË¡·+—{ÖF•Ÿw¼Ù”â%.þ˜¨ÈžâåÔ˜ ÙŠ E[O,:[8p¦˜Æ'™ôjeÀLhœŠuR„9ž Í…ÔU®¤­n¹-m”PzµRr­RIŠ%!Å‹x�¾·«Eí€3Dj@Ú\Q™™¤…¬Ë/s:&ƒD…ëÅ,"‡mHGÛùMg± æ0•J¹Ûj†u…Ó¨Ö¥—ÙÃØ©„èþ”(i5ÙI�KxJx ÍÄËVÑëW 4ÙSˆ“üÑP;ûù±9mSTÆMóÏjɰçiG¤éQÿ>ŸÛéÕʈõ®}õ_ÍÏöÔ?µJNHº¶?µæ‹»‡w^£âæC±°Lâ�A²[î'“¯Ï�0pdÒ«µd·Ý{ߣhuËiCW_Mòp'½Z†i“�>:Iž—;¢éJS’µ <Á}}oÌïBNdæÃþ{´ïµÚŒèµ/ítPÏ5Ÿz¶óh›òg�sËSßCŽ3õh°˜FH67¿¯^ ;Ä&ùÎÿ.±–« P;‹/Ä-¼ÇF½êÚaJ(¢~»¨èeEíMûi¿ljX�¤M@bq’‘ûÙv(‘\l.›6òÐéÎê§Ïæ�Åzâ¶„Žôj¥`ºw{ìÖ³‚§¶õª™F3�»žz¦­èý©¯¾~“Ú6­*8œBFOÙî%Ç›—ÔL¦ Ë/äÍr3PpNÉ+Š5-,½ZKÃS¶}´ù7ïh;È´ÅmnÏfê2ptÓ«•²õµ½­º<ò�(kã§KTS<%óÁ…7¿ùäÑ…»žÓ³ö@ÿÈjsˆÖŽÒÁæùmH%×»þê ëŠÒÈ×J¯V ¦¦HÌßr±o]î<Ÿ,g_˜é=}9£ HÏâw«ñ©ÿEƩЦ9À,h)i‰…Grt•5Rs�>éºxçöÄ×âôj¥‹ß}ÆÃ£øÛò_¬pѽIMˆ1ý`"y¾VzµÒš &Ì*(0Ö4È�h׎L6 Í7Ûyájqs}ìo:ê‘ã¹Vô¯X"¤ ‘“�§”}ÝÐÂïf³ñLq%‰[^±¬6¸š\t¥Ê#Æ’öìñÑ«¯+-CÜì¥òyãL&¯•ååµ<¹Oít»{þX�êÊËD‘™½¸�wÌm� q¦sWuOöÃ#&ÿÀëuÔS¥ ftË]ÕCu¯kÓŠxgS¦½—?y­Œ™ö9«úæ…GܶŸ×�o¯2<ÒlWcë½bÏAÍãã­Î_SîÓ¶Ô$­•)ïîÊÅ~fzZsíùÖ½ÚT$¶=ÏRªìÕ§»K Hº�3mÔm/;NÍ]j[Ѭ¯¬þj‚ðˆ�¥„a!Ì R&ØÜcºñ�üѨHA¯èŠ­+i‰¸,QÎÓŽ¶Oˆ>9Q& „-â›­¡Ë®«{äѱ«&OCoÅ]ß„êïIV+K¶œûº»v¼m“HQªï­ëþê¼ö^¶Ùz§¤¾9öïN%±‰OÙ4òåyÄGIÕgg4‰E’…ûÏ’ÕÊ)‚aÿûö*·žñIÄ7å§Ô¼W%/Üê)z76r’dzòS1§•¬ÖRˆ(GNÇþ—Š©/&/* ?Ëùô“¡ê�4ƒIJxƒÓBä¤o|3DD9|’Æ1R@R³¶¯Ö+ðI;hd$²ÀÞ*/Xvœ­ Š 'cCM@æåE£Q1 ÖþŸ91gÑ'¬ˆÿÝãVzG|ýRÌYÁß~óbÎ þwÎJçí*Û3(@mHzˆ�ô!è! @mHzˆ�ô!è!BÐC„ ‡ ÑÿÛ»&¹>¡9IEND®B`‚numpy-groupies-0.11.3/diagrams/aggregate_dims.png000066400000000000000000001301351501360642500220430ustar00rootroot00000000000000‰PNG  IHDRÑ6W ýsBIT|dˆ pHYsaa¨?§itEXtSoftwarewww.inkscape.org›î< IDATxœìÝyœ\U�÷ñϯºÓ�}%+{ö@Ò•°DP@H#.(ú<Î8¨£�Ûã2²Œ€ˆ¸Ì8Î8Ž:£Ž£>.ˆ(Њ:ì²&��„%$! ! ÙÈÞé¥~ÏçVºÒ]]}+]Õ·»ëûžW¿šº÷ÜßýUÅé[¿{Î=ǀˑ¤¬v÷gã66³À´2æ#"""T©¤“©`Vdûºv‹ˆˆ$Fa‘˜TD‹ˆˆˆˆˆˆÄT�gÛF`ko'"""R!Ž•0^ ðR ㉈ˆH»qÀ¤Ü ùŠèÍî¾¢wò©,fv,%.¢Ý]E´ˆˆH˜Ùt:ÑÎ-""""""“Šh‘˜TD‹ˆˆˆˆˆˆÄ¤"ZDDDDDD$¦|‹‰ˆˆˆH_`œœ�t""dNC¡*¢EDDDú® €ï%�„ˆHy ÑÎ-""""""“z¢+�ñ`BÒiˆˆT�_ã,H: ) Ñ}�‘ŽÀYVæ³}8¡Ìç‘v/‚Šèǘ Œ6἞t:""Ò{TD—ƒq"p<ð:Î#1ŽDxæ ({-"""¹ŒZà¢èÕc8ÛbuáZÿôn=‰IìgÁ6UT-ß–3âÄK“~ó*Vݧm1qË[qË·œ±·ü±·¸¸ßå»\ÅUqB "º˜PdŸEûR‰-ÀšèæîÅ@vhû.Â÷€ Àq⼜;oAÎ9²Æ®¡ˆŠèömc�Ó�a9íGGc<€w¸)]œé„Ïã)ˆŠhcHô¾&FmZ 7öËsü«„Ü)ŒÍ8ëì1ŽÎ$àOô Géz^D‡‚÷DB¡û?„»Òõ0ŸN¸sœÏÑÀ±ÀfÂ�å 5:î�Q›�—¢xÀ·zpO‡X³^wç(Bq¿ØA¸H¾ ô?]‚sáB|á3_<‹ÓÍzpn‡#'|™K¸ÞÅ9‘P@o#\èEDDâ3ª ׺Zà9ÂH­ýU„ËÙkU>— ÌÀvàµhû9„z-ð@t}3àTàlàŒ %¸ñ;žð½áQ`côN'Àç`¬Š†f—Ê™„ïÛßI²�jM"\·ÇhélÆXÌÞˆñ+œVŒÚ¿×<Ôƒô""ÒG¤zttâtfôê!œW µvvA4Ä+¿àp~ƒó8Î3ÑPîÓ ÃœŸÃY–oðGÂ�é“0Fõ(÷à8OàlÆiÆyx8ÚwJôþJe¡gzÎS.¢Î~`y§Ö~Ð$'³1FG7Î&|”ø‹‚ˆˆT†™„žÜWq�®Cá¹à0̸¥À±/¿Äù3ΜW¢Ç´¦û�?å\ßïçqÖ–,?ILO‹ÄÑ„ ÜÕE»ÏS<†ád/tÚãžC2³P=•oÚóµ„áZµ<\¬§²C¶—l•+<ßµ”pSá�„!g¶ûËÄe""Ò·d—rŠ=j÷Ä�¢»]öº½òÀ¼&Ë^ϧäÙW¬½Í‹D¯³CÅÇu:âÐAø¾±2vºG7¹ÃÍîS1Ò„Ñz;#ÌDDd(E áâÐsaˆY¶píj‰‰ìö‘%9gGá˜-®KSD‡!r#¢W±×Ž‹Ù ]U‰â� \L[»¸KßµpÑÍ-š_èt^DD$ŽðœröúYªkw6^WÏúf·×D7ËË!{m-åcXٛ߇ò÷b80Ã÷VüÀ³ã""2ôôb“í±R°U|{£ßÕƒºh“=×îÛ­D9@û‚]%Š—½Wýœu˜�$�³åÌh22‘â„›°¥¾vw/»}žÞØR]»³7õKu݆ökwÍ![—sÜxìÀj%""2ô´ˆÎ^¬Æ(zã ½ÏÙ‹ñ˜.Ze‡We‡‡e/È¥Z®k႟¡s¡~hÂócÙ;ÒÅNˆv.ánøBÂ:Ù󠊈ˆŠìµ{R‰ãuuÝÎ7|<{íîùw‡ »äTWCÊEö}wÝ­¥ ßgˆ¶Î�Öå‘ §Eô³Bƒ³O·3FÂ!ÝyÍÎè}J§=!æ‘„™2×G[³å±Ñ0µÜöcëP#;{èKÖfî©MÑïóìË�6¦–ÛÌ',ëÕ �ql s‘Ê‘�Ôsftã80R§AÑ£�ÖL‹–sì({=%g[öÚ}ðD`a´ÖŠaŒ&,ÍÕ ¬,êØÂ²×íé�: Â\'�‹â0\ýÍ„ïWã¼å4ˆ°t˜ˆˆ =ë½ ë7? \D¸N(øFÖr<”"}¡p<c7°„ðÜÖº–,Áôï  œ‡±”p±:†°>e¡çšOÇx!ÊyHtÞS½Ð !÷Bž%Ü8 £‰p³`0a²°S;µ_lÎ#|1y0ê¥ß}Þs ïuƒÖ›‘"½HXæj$ð.Œ „ëõxe¾g+ÆrÂ5ô2Œq¶Dušp�ÛÇÁó{l Ìê}:Æ^B¯ï$à4`l�³�qjt| a ç³iÿnÐÓu¨sß×ZŒí„žô·b<½�‰„îùn€Ÿm_–³œÕc„™¾Ç8ç¹’å(""‰èùhgÆ�ó é²CªV ÑãO¦åì‰â½ ˜ý´æäº‚Ð+›mŸÁx2j÷~¿H¸pu5„êhB±�k?ðÇh�ëÒqÖaŒÇ ŸëÙ¯à%v.""½¬4Ï;/c¬"<¯\ lÇiÍ™H#÷b±�Pw½dD¸û{ábtáb¾xgUžö/E½Ö§žÞ ¼Œó Æ Â…}o§ãàw„;ï‡îÂoŠŽëi½•|ïÑyc¡xEèE_¼zPÛ0l}#ù–³ ½ÿ’-º�!%½ó.""_¸n4` &\»wáÑœ$aBËVü ëæ"Âõ=aFE݇q¡àK(&7ÏuZ[ÚiÆø5¡Gwáöà9BÏïÎèØŽ–«×Ña„ë躜^ßžXD½Öþ��¿f æ aXúRBOú88p£{8áÚ¿.Ïû]M Z“óþÙHF¾¿‰¦înrÌ1³¸£Õ®ŠÙv§™ýoàeˆ]®œ7п]ùãê3î‡qç0磄À±•n©‰0ãgÇÂx|ô{gN»m„Bº»x»)殲³žöç¤s·/-pT^†5— ½Çð|ÔKyöäö®ï<èuçÛ î/ÎŒó³/žà‰±×q]ìƒůÎÃLšVP9b—+_Å-lÅ-lÅ-oÜ|±‡÷·¹£Bñ»ñÀë0—H¯aq¯•Î2B¡§í~àÉ<{^ƒKB9khŸ?¥tœ…]lßü%ÏžUÑO¶]¡ïà,>ô䶉M¿îîq.3LüçÛÝÝo‹{~3û›rÄ.WΊ{Pì¿)GìþöYè3V܃ƒr1½^D£wU×´tEÖtáîr)'úè=á½�QäQ ¢"º¿˜ýpgñ süýq–#v¹òUÜòÇVÜòÇVÜòÆ=ÄØ}ƒq<°ñ aÅáæì—ˆç“H«$Œ3 Ï{ǵ¹,7ÔEDdÀ*EOôHàb MÀÑJÒ4Œ0)ÖS�†5õU¿Žf¡‰ÌDDDú‚ÙÀ(Œ=„g¡k ×ó*Â0êþ[D‡÷R̵»ØÙÈED¤Â•¢ˆÞMX¿x<íKUì%<3ôL‡I±úжèÇ ¶ =Ê ½‘�ˆˆH/ZCx®x$á¦w+ᑬÀâ/ñX Âu»û¼œGÊž�ˆˆT´RÌν�0AWvÝÄT‡8ûçö¤SIŒó8Ù¤�Ú>?bÌyžþÝ;.""Hé&¢µŒÛJSJím„ÙÊED¤w”bæèòéë´ˆˆHSÚ"Zú>çŤSé¯RI' """"""Ò_¨ˆ‰IE´ˆˆˆˆˆˆHL*¢EDDDDDDbR-""""""“Šh‘˜TD‹ˆˆˆˆˆˆÄ¤"ZDDDDDD$¦ê¤�Þ•6N'�‡ˆHYÛèlK: ) Ñ•ç.à„¤“© ~œt"""R*¢EDDDú‘a {j¯íõnš½ ÔÅ 9ÝÌÅl» ¸ºL±Ë•³âú·+\}Æý0îÜqä;xGÌ��Šh‘~$Cæ´Í»{¬/¥f6 ˜óôƒÝ}Ä{Dáb—+gÅÕ¿]oÅÕgÜÏâîfwÌS·ÓÄb"""""""1©'Zî¾™}±‡'Ç®çºßÄ=øX~uQ5šã´-Gìr嫸å�­¸å�­¸å�›/vÙÊ=qO%"""ý�Šè>,m Îf“�QÀ`%ð³FgG N³¾Ñy(ûÂììIż„‰�¸{Sœ¶åˆ]®|·ü±·ü±·¼qóÅ®bt1§‘~HEtßvpoû¾’6.Ï-€EDDDDD¤¼ôLtßÖ< |xp*p>p0øeÚ¨M,;‘ £žè>¬Ñ¹¸¿ãö´ñaˆ÷qÀÉÀÂ^NMDDDDD¤"©'ºŸI£ Åó«Ñ¦LGDDDDD¤¢¨'º�Kã ‹¶¿˜ îÐdP¯'%"""""R¡TD÷aica ªc€½Àà9`pP—Xr""""""HEtßv'¡€þ ð¹FgKvGÚ8Ñ""""""½JEt•6¦û€O6:;NIDDDDD¤âib±¾ëèèwSôÓÑ�^ÌEDDDDDDPÝ—-Z€1ÀÿI5iãØ´ñ à²$“©DÎÝG5:»ÓÆ¿ŸþøRÚØì^¦&˜¢ˆˆˆ$às|ŽVZ ¶ư±ׯ‰÷�÷Å:w1qË[qË·œ±·ü±·¸¸§rj¬sç2Â,Ϲ–¸ûŠ¢#IÉ¥�jà€�ã ÏGß \|8¸µÑùS1_ä൥ÿ³ÑùHö…™M6‘æwÏ7ܼ“rÄ.W¾Š[þØŠ[þØŠ[Þ¸ùbW1šÓØÞ±Ù�çs04gÿ*w_793; ˜–³i¯»ß÷xégŒ�ßK: ‘ ò,άì 3›WÚê‰îÃ�Vàfàæ´1²Ãäb_L&+‘Ê¥žè Ó±'ÚiÙï4ín�µ±mlÜxƒ8b�³7Ö$gÅÆ®bìVK f%ÄMúß­âê3î¿q{öo—¢†#;6SO´:õD‹ˆô6õDKaÆ ZcPmî¶*Fâ+¢}‘±Ç%³â&þïVqõ÷߸åü·)V+°?é$DD*H·sUD‹ˆˆˆôUÎ�&�†ˆˆ´ÓW"""""""1©'ºòü˜˜t""äù¤‘ÒQ]a�¯&�ƒˆˆˆˆˆH¥áÜ"""""""1©ˆ‰IE´ˆˆˆˆˆˆHL*¢EDDDDDDbR-""""""“Šh‘˜TD‹ˆˆˆˆˆˆÄ¤u¢EDDDú¨´qpMÒyˆˆT�—�÷j "ZDDD¤ïšœ™t""dpw 4œ[DDDDDD$&Ñ"""""""1i8·ˆˆˆHÿòk “}±›Ïnáµ£âXËq‹‡’~!î‰Ê[qË·œ±·ü±·¼q;ÆÆ™ÔpLÜCÑ""""%7×Òªaÿ1°ùv§­„áßßè4e_˜½é6 ÖMà—îþµ¸'*WlÅ-oÜrÆVÜòÇVÜòÆíûh~ÌØ"‹h ç)‘:ã?ÓÆÆ&Ø ¬n… + ¹Î¸gŽ‘N:?é9õD‹ˆˆˆ”Î…À8`ð àÀƒË20wŽQ7ßY›h†""Ò#*¢EDDDJ糃àѧœ­Ù WU+àN`^> \—\z""ÒSÎ-"""R" �»r h€Û�6‡E/OK -)!õD‹ˆˆˆ”X�q p¡Á4`p|´k|bI‰ˆHI¨ˆ)‘ÙÆI·Y‡g �‰ˆÈ áÜ""""%0Û˜íð¡€~¸ 8e0 5¸8ÙìDD¤TÔ]aÒÔŸ M:©�7ÒМt""½!ï3ü²Ñyoî¾Ù–PR""Rr*¢+ÏO�’NB*Æd`cÒIˆˆôƒs£ÿüM¢‰ˆˆHYi8·ˆˆˆHil‰~Ÿ—»ñ8£ÖÃÐnÔ-"""Rw9\\=Û’�‡RpÊx'í³s‹ˆH?§žè>h6—¿/MýÓ¼í¤s‘xá‡wÃ>dðs‡ë�à“ §'""%¢žè>Èñ3�ÿ™ÅÀŸfqéñP5+…½ÜÈÝóKu®×ïØÀÆ[_`hݨ›ŽúѬ†8Ç-ããßiÛÙ27NÛbâ–3¶â–7.pðµ˜mEDw2À³�Ëæ:dR°`$ܳ †µÁ’*Ø™tž""Ò3*¢û #u‰Á×ÿ P²"ºuk3û…ëù¾E;×nùÑÚE±òyÄvÅ=G1qË[qË7Mý‰q㊈ d œß¿ë°yðP‰Nñ¹´Ñš}qÿvR ëc8„™ç§‹˜)¼\±·¼qË[qË[qË·cìÁœÿÀˆŠh‘þå–ÜãùD1Ç^ýÄR®ØŠ[޸匭¸å�­¸å�{±;Ñ3ÑœÉÛÇ�ÁÛŽ4¬à} Ã,ÍåSæråØÞÊMDDDDDDz_EõDϦþ¿Îqüõ´5î§êÓo[ÔHÃ'.à‚ꌸx/p@óöÎfÞO »~> {³ñf1ïð|©Žúw€n¢‰4õ»�?gðo.âž§gRlܶ¹‘»Ïé˜Sõß4¸Ì°kp÷oóå]Gý=†Í0xGšú ²ûþ~! w—ôƒ‘¼*ªˆv˜•"uþ~ì? Ž Ûm À ®^Ãðû€3Ü6ü-Ž}ÄáTÃ.tÜë¸bt û0|ð°c# OoK‘zxÚhUG�Η“ÁaÀQŽë*oƒýà­`m@Sv_*¼‘�©ØŸt""¤Û¿¹UDg9~­Áǯ1R vÔÐôy°3 ¾½�Ù×87eŽå‚Ácq'pAóÞüÒÈ\ L[º�ôÙ¶†Ù,æ]>¡Tù6ÒðÎ:æ}*L,Æ] iø»RÅ‘¾«Ñù!ðäó‘vYDßO±þ³ó™ß’Ý0“·O¨Â> þòHv]—-ŠVñ`ÓlêÿÉáÃ.%Ñ'8ø}¹mwà½únDDú3 ª€fàw÷D+3«†MîÞœt>"""Rz•ZDÿ>·€¨¦¹ÎI 6Xº�‘Ófr9ïOµ@ǧdHýÅð÷;¼o6õ- á�^Ì_D¤_2³qÀ„çSØ ÉyÝß�L˜Oc)°6á\DDD¤ *µˆîÄI~S_…×wÞà»Ýá5Tý®™ÖÕÇ8Ü—¦~=ðð»…ÌþUnï´ˆˆp<¡`^¼ìî­ffý¹ÚÌŒÐû<šè!"""—ŠèltèñG�Ôí]µJÁ>€§øíÖ¹\Y×DÓµÀ‡�)À;€wÔÑøÙÙ\þ· ¸{q¯¤."ÒDÅæ¨èåZwoèÏtdpdÒIˆˆˆHïPq|¥†moäîÄ9æqnßÜdÜ|óLœ”‚Ë€�‚ÏpüSÀÕí­MŸµˆT,3 R„Ì̆D»÷»{&§í``P ìv¹{[‡x©h¿»{SÎöZÂ3Öƒ³ûͬŠÐS\ lëpL0¨^w÷=E¾½Í9眽O Tص[àpÆê‡æ®Ý�hèösÀsiêŸ$¬ }©avWlm¡Ç7§u˜w Z£®šA‡r¼ˆHÂŽ!L&¡¸½ gßãÀëQ1{pT‡c[Ìl±»oÊÙ6˜ ì2³Ç€éÑqµ„GkÚ€s�=fö<0‹œ¿Ÿfö‚»¯2³)À©„ ÎÜÌžs÷ØÏ3»ûF`cw*¢EDD´TÒ ô‹˜½˜Lrü+ÆÍ�>›™¼}B—ÿ5ÀlêÿîLÞ>®cÇ×Gÿ¹ËqŠßnÛæP3‹…gå¶­cÞùÀâäçø+†O/ê�‰ˆô ;€ìßÇ6¤[ÙŸìzŒ§ á=À2`1°†pÃwvTðv48›P ×Òyr²!QÜf`5Q± œhf§ŠëÝÀ*` áyí“szÉEDDD¢žèˆsS¦ŽK?nT=æØGêX03Íå·m/gH�ÞXžy~ø ðöZo®£þ—)ü� ¶ìÃ>`ØÏs¢7cd~VGýwSX““y‹a7¿µÏdhmÎH3ïÛà1R“3ØÓ ¹û/%ü(DDJÎÝ_3³Ý„ù#ZÝ}iî~3›DR½x<û¼4°Î̶iBá»1wè7¡W`¡ή¼0"x X™}öÚÌf&›,q7)£}gEyL ð""""Q�c!÷.ªãÒs�ªïgƒŸí¤°ö&»pì1ðÓ >êØGCÈü×4joÉTMÛ[©: 8ÑàËa¦ok»üdàíÝå6Ÿßl˜Íå_pø؇À>º[üo�C.¢kŽÂÈ‹'0lî˜SÓÔ_ç¸1ï=ü°¶m-Ý7,2n9c+nyã3c¶ÉçØè÷Ë94†K›Ù¤d� 9»÷�v<&ÌaÀ^w_Ñá\ÛEô¶Ü:gßXÂlÛ"Rbiê«�¿ÞMûc•îæFL: ‰Ï€+:l[’ç Ç€0›ËOsÝÌþ¥Kùö®Ú]É•U+Ù;+Cêç¶ÇñÅ»h{l9÷e‡2‡·ŒrjÏrª¦:>X þÌBîYÞ1æ ®¬DÓ;SpŠÃË)ªšÏ�+çPb›ÐË–p÷¦(ÏéS2dV.âžWsãÌäòªà` dÖ4Óò@¡÷ÒQšú�â¶é¡É�4lì¾™T3 œO˜Hì:ì»�0±×CîÞiN 3;‰Ph/w÷åf6šè™hw4Oû‘DÏD»ûÃöMêEô“öMNÖ¹{Ñ+,˜Yš0¹ØÒ®ž«6³‹8¸H_å8ÇiÀ´œM{ÝýþbséMiê ¸¸…öù$ø—F>“t"’Ÿ™M'ÌŸr@EõDÇ]rêvnoD?]šÏŸv±¾¸,åöfà�c4¼¼Ø!Ï„5T;y–»—ž̬šP@C˜¹;ŸìöRôšàñ�&‘®E#š¾B¸y%9c¨ˆéG4±˜ˆˆ$-w20ë¢Mv{_SZ¤b¤©Ÿ›¦þaà÷¨€îšqbšúQI§!"ñUTO´ˆˆô=îÞffÍ„ÞèÁ„Ù¹;ÊN VìÎ"ÒËÒÔŸ Ü Ì+Ьø3ík¬w’ÙÝ:dßs»NhÝ´ÿ(¼ËlXµ5×=tyí‰ÃWYµµuÕ®·ãÙ #.< «é¶Ïj+a·Ý5‘¾AEt幄° ŒHoØœtÒoì¦}VìUyö�Ïi'"}Pšú©ÀÍÀ{éz´£¿nl¤ae¾f6¸ø8í7ÐòÙƒ´¬¡ IDAT| øGwï²íõxc®:œ)·œ˜±šTœQŸßm¤!öÚô"’<Ѧ‘†|_NED’¶’PD-cµ/»ÃÌŽ† è×ÉNDº”¦~ðàĵۻr/p}# yç¨1³aÀ§�Ïzf»Ò|ø²»w9yeñF^:‘)_=чœ:ÒˆÿؤSégTD‹ˆHâÜ}³™m",auŽ™m L&6†Ð;íÀ Ùµžû3; ½ý>2ê­‚0£¸zÐeÀIS?ø<ð) Oú÷(p]# y—ä4³BþÂ߀®dˆz±Ýýå®%oøÆ2å«'1윱ÐõÜ"2@¨ˆ‘¾¢˜NX¾éèœí»�g ¯LØh`r‡m£hï©Z‹†¡Ë’¦~ðIàÂ�®®,þ¡‘†ûòí4³ð>ÂðcóµÉñ;à -=—D¼!³F2åÖ“ùÖ ÝœNDÑ""Ò+¢õŸó~™Žö;°ÜÌV#ó7ìr÷NË^¹ûëÝÄÚÙÕ~wßT`ßj`u—o"ÿ1‹�¢×”éoÒÔWn¤ó�£\+€€_5Ò�wôˆ™Í#,{5£›Ó>\ëîy{±“ŠW;}“o9�1ï>\ýÎ"HE´ˆˆô)Q1½3éE†kH1’"Šç–�ûÙø¥—Øúƒ5x«ªg‘�DE´ˆˆˆˆ�¦~&p+piWm¼9Ö¬eã-/Ѻi¾&û€o_w÷m…Îgf£ ”}’Â3|ÇŠYªxiêk€‘áFRLˆ½Xж£…×þi%¯ýËËdö¶Å?PDú Ñ"""".Mý4ÂóÌWÑUokÆÙöóWÙpÓ2šWíÍ×¢ø/àKî¾¾ÐùÌl(¡Ðý<…gøŽ³TñÒÔ‡¹�/aSLñìû3lþÎj6}e9­[›ã("ýN¾"zj4FDDDJopÒ ˆd¥©ÏöÜålW;6²þ^¤ié®|»øpƒ»¯(t>3�ë Ïð+f)㥩¯Çù Æ)Å̸ímζŸ®cãMËh~%ï#á"2Àä+¢‡E?""""2¥©Cöü {ÞýðVÖ_÷{žØÞU“ß×GÏM˜‘û¢nâäó(pͳÃî[l8„ãE¤S-"""Òs˜3(ÃÄ)PÝÒÈÝŸNJšúAÀ‡Þ'uÕnÿK{ØpËlÿu—Ë1-!¬·|Owç4³ngø.&f©â¥©?ž°æõ»(b¶íÈbàúFî0¬ËÏRD®jt÷LDD$I;’N@zjÊ4`‘ã«�Næ ÑDYÙaÏS»jײ®‰ _Zƶÿ~¥«å˜VÖ[þ…»g �Ó̺�Ộ˜¥Š—¦þàFœ¿Å¨ê&VG«£Ø?o¤¡àû‘�¯ÚÝŸL: ‘�ä.¨ÞÁˆF€íì:c6õviê/# W>µ«6m;[[¶üûªµ¿²|}fO[¾âp0¡·¶‰0�×»Xoyð5àlBÑžÎÓn(p$°‹Ð‘ÓUÌ’Å4¹vÐáߣ¶È¾çÍ„þ{�4hÊm4œ[DDD¤äv±Ë`ÄñãQÄBI=—¦þ „aÏçh¶ø—ª‘ÕßxõúvrýÁ;Íìà&àP°×v¡Øýwwß,~бQžx“€ãÊ/5¼š‰ŸŸ¶oâç§›Õ¦Š�7ðÏÀ?7Ò YÃDä *¢EDDD€4õ³=Ï—hÖ üpk# ¯uÜifã€k��Sx9¶=À·€t÷.IH"žÕ¤ÿ±cš'é„–ÔðêbWœiþør¾ÏGDTD‹ˆˆH5“wQEËàë¹ç ³™Ì;8¥~7Ÿ†½†Yšú‹ÀgÓ[gøS hø]ǘ3¸²¦†¦w;>#cœÔ+Ž/ÙMëï—sß~€4óÞ 6ÚiäÞ5¹ÇÏ¡þ° \âØ¶…Ü}o¾¼ë˜w\Š)z�3pUšúìPà-�4ü¾D!ßú鄉²ÞM×ee€ÿÜÔHÚŽ;ÍlðiàsÀ¨§k¾|ÙÝ7vÕ(‘x)cìû�h;ü'ï­W3¨)pÞŽøpC# «Š8ND*�Šh铪i®sìŽýi&—¯›Å¼ŸÌÈÐv°fõ‡Õ1ïGŽÿUt˜ƒ›³©oØÆ®÷gŸGžÉå'Ô�i;Ú°hòiÇ€To&öi‡ !õ¿€gjd~¾È[D©78þÝœMßÉùïù„u‹{,MýÂdWWSø;Ý�Àix¾S®f5À‡�/ ÄÈ¿nt÷—»j”T¼ÑWLöÿuÊΚ#‡ŒF8o>¿®k¤áÙ"�‘ ¥"ZDDDú4ƒÓªð'Ãw;S n6nNÕÁ=@Øw õßRlZŸaòÉ`ßt¨ÃðÏ_¨‚ÛÀŽ~c¤þÃiYk¤NË`WXèÅ-‰6üÁªð¬î‡üíê6¼ …oíiü4õc�k€OC 4}€P>Ýq‡™¥€÷7ÇvsÊß–ŒZÜUƒ¤â�xÓañ�S_|âðÑîñîdÏ“Ûyí›/?°ýöWßZÌq""*¢EDD¤¯›ü¬™æÏ/åÛ²ÓÌ{?XðýFîþlNûggpñ»j¨y ì³ÇñÖEíà'»Rìû?óùSö¹Û5À=³¹ü_K•ì³4¬šÃœu¦DE´ß¹(zÞiê‡ÿø{ Œó kÿ)ßN3›GxvzF7§| ¸ÖÝÿR¨Qñ†ÎÍßžñú°³ÆŒFwsÞƒ4½°› ÿð¯ß¹ÂìÛ½iejhÕd¬â"ÒO©ˆ‘>ÎjäîæÙñA€Ußê¸c)Ø–æòùàçeÐ1-¤öTÑ0¬…‘#è°>÷îî²W´/HS!ð3 ‘Ø ¬®NSuWö�ß·t×iÇÜ6û°BRê^ròˆ%5S‡®>‘¦þùÚ%¯æÈÁ»†Í;‚"‹ç–uMløâ2¶ýø¼-ïZØå´‰ðÜú÷mPª· w)1Ñ"""Ò§y4º3; �¶/ÖQßy/L¨Â§6ò›ß§©_ÔUÑútšú<ÜÊþ§žåþ=å˾d¦Ð} 0Ž.†¦:rƒŽ,4úû€ÑÀºk”`¼¢žynÛÖÂÆ¯-gË·W‘iÊ·vYí¾|ÓÝ÷¤óüoUDúÑ"""ÒïœÉÛÇõD¼±@Ó× ¯HÁ2ðïàç×8\SEíþÙÔ¿šê¯=Åo{ü¼rµ&�@“ÙÛÆæ[Ŧ¯¯ íõ.îÔO3ð]àVwßÒÛ'‘òR-"""ýΪwÕÐÚT53øä¥Ü¾»»cæÓð"pálê/ËÀ› {ø ‡O´Ðú6ÃŽwüÀ8_'ÕÕrQIèñ3Õ•Â[�­?ZËÆ›_¢e}SoŸ>Cv£»wZJLDÑ"""Òï,åöæ4õk€©ƒØ3X÷Øhýèߤ™w6ؽÀ‘3¹ìtàiÂÚŤ ¶¹618UŠ8Ò ‡×ïXÏú/¼Èþ—¡/p�»/Iâä"Ò{ôG]DDDú%‡'ŒªÏtÕæX.<ƒ+kê¸b´qs§ï=�Üó„c¤HÍÈÀê(~§¥•Úh'·ùÌoö ¦­Øu‹¥HMÏíbÙ�²êÊIÐOç¹ûe* E*ƒz¢EDD¤_rüÃê�+gsùÆZjozœÛ÷Ç[kGP}ÙFÜ”¢é²6¨›Å‚OÕñ¶/,ä®Ç³1ÎàÒIFÕi–†"…/v ðÿ=“wü÷³üfÝLÞ>¡ŠÖv}üüXn0+Cë{�˜ÁÅcs—éê¡ �W»ÚùúíëÛpÓ²Gãzæ˜�ýãYÿÓ]»$c™9’cn›�wßîǶ±wþëqÒ*3»hq÷¹½zbIœŠhé—qÏ«iêÿøžãŸÜOÓGÒÔ/ÚFP= –ÛÞ`.dHS¿x¨�ê9àƒ¾×-sµ�Ý¿È뀣ªhYž¦~cL !üQ°ngšŽÎø=ðÿ¿5ZÖ©¹–šMĘ©:¦•�4¬îòìï¶Iq5½¸ûÕ­?^ûbwí’ŒY5zPÜS—•™� |�ðïøÑ„Ó‘h8·ˆˆˆôI[À5¬Ë!²�4ü&Å ™ÿå¡ÝÑÀ)›�§°KæÓ°v7­€ÌßühÎNÖð¿^Ä=†„¯âÁ&§ê¯»l?0xÒ±w@Õ§BN4fÛ§H퉶=�›Û"~êðqà9°Ñ@­ç'ý‹™�lfw�Sº!"Ò©'ZDDDú¤FîyxKwíæó› ÀÇ µYÎ}û�Û¢Ÿn-äÎÕÀÛ»Ø}PNOs×+ùòŒfúþaô#ý”™ Ü ¼¨J8éTD‹ˆˆˆˆt`fc�ë£ 'œŽˆô!*¢EDDDD"f6øp 0*átD¤R-""""Ï̪�«�›€É §#"}˜Šh©hföNàVàø¤s‘¾OE´ˆˆˆˆTªÝfö4pzÒ‰ˆHÿ¡%®DDDD¤Ò,.þŒ h)’Šh©+�«€ÙîþǤ“‘þ©ÚÌNJ: ‘ ¶ÝÝ7&�„È· ¸ø¾»·$�ŒˆôoÕÀ‰I'!""RÁV*¢EÊc'ð à›î¾'édÌìü™».ž®i‰Dú3 ç‘�¦øW`š»ß’tmf³Ìì÷ÀƒÞæUIæ""=§Û`""""2Pd€Ÿ7ºûš¤“1³i„aäï,átD¤DòÑm„?@"""Rzƒ’N@d€º¸ÎÝ—$�ˆ™Mn>Œþ^dÀÉWD?ïî+z=‘ `fC“ÎCd ñæÃÜýò¤ó0³‘Àߟ†%œŽˆ”‰†s‹ˆˆˆôC[°v¼}Øš 4l&ÞÃ*3›£ÝÂL×q”4¦UÛ `l¾}µÓ‡mˆ™SY˜Y-ð1àzÂç."˜Šh‘~hãW–?£Ùýî~qw�Ìì.àŽ˜ñâÆ%�™¦þ,à‰8çî%K€§ÌìÀ�£’MGDz‹Šh‘øV7?&�‰f#"½NE´ˆˆˆˆH÷¶_¾çîÍfšp[¤©ˆ®$ÆaÀ¹I§!"RAÚpîI: é‘ÝÀ7�o¸û®¤“‘䩈î †à”ó�ó àÎ2Æ‘ƒí'�„ˆ’àûÀ-îw25©*¢ËÁ8˜ ìÅy>Æ5Àû¢ÿþ�²å%""""Ýqà6àw_™t2"Ò÷¤’N`€šÌNJ:‰í~`¶»¿W´ˆtE=Ñå±Xeš-""""¥ñ p­»?�t""Ò÷©ˆ.g°*é4bzðlöÅ%\ò¡Õ¬þLœÇ2ö®¿ð—ëâ´UÜþ·œ±·ü±·¼qóÅþâ3Ä:•ˆô ë€w¹{œõ¬ED€RÑÆHàà0 ØlöଋÚ� wÞ�ój´m0X…Óm«¦ã€!À6àUœÍΛ�×~Žƒ÷Kxæx5ÎþNÛÀ¢¼'­À†¨îég1Ø…³¾Ã¾AÀ±À$  x�Ž·182zµg_‡ýÕÀ´èÕœ¦d»çÅì‹?Ø6jÜÁŽÜc QÜþ·œ±·ü±·¼qóÅÞL1§‘$™Y°G´ˆ«4E´qN·ÓÌ&Å·X£Ýk1ã•<æqÎ7üüqy÷mÿź÷PªÇðݽÀuî¾$á“ö嬯ó€9kðχ‹ˆˆH…9yù›Îm¤auWû£ÂtaÉÌî\áîWw×('f%�¹þº8þ‰sóîË4e’, Ÿ®qwÍY#R!z6œ;™Ùe­Îǘ‹qÆ™À•„e�Š•-œOÃ8âÀÖÐC|>áÙ–UÑLݯÖ¥ŽE3`)Œ£¢ ç9&*ÌÃä_Æ4àŒhßÓ]uh–F¿çDk=‡"ß8¸¢SëpàÍ„£¿àìÅy•0AFð¦CÞEDDD¤ŒÌìRà4wŸ«Z¤²”b�èg …ê à´œí; Ë^�B(rãq6c,f—FkCï! c®% ñ~<§} ÆÂç7aœz ·zÊwq¶Ó EíÞ(vöóxgcìœãy‰ðYŒ®ˆÖ£®‰rÍ7lý,`,a®—r¶?Iè�? ¨”8Oé‚™� |xðфӑô¼ˆk?†ñ"0‘öâu0�P8æNе�°Ös×C‘�g0^#ÆãÅäëÀrà)œŽË9ò €yÚ>Pà,ëðÃÑKÇY¬Ë³½•ðlsx¾û४rÚíî)¿ý³‘²1³#�›�÷Sª‰lE¤_ëym ?°N×�ŠH*EOôxà­Ñðë×â‡fØ®žÃó>óÛŒÎ+ò˜{)f +é3Ìl(ð)à`TÂéˆHTŠ"º…0qØÄè'wûãt^Ωÿp6ÿ‘t""""R^fV \ ÜLN8éÃJ1±ØFŒÖ0JX’éu`Wô¬r_ôGÂ{ï¯=ä""""R"föNàVˆ–K) 4‹…bykôÓ÷y?ɳôVŸN: ‘ Rºy:D¤v›ÙÓ„eOEDb)ÝìÜÒ÷…YÃÿ5é4DDDD¶¸–°¤èÕ ç""ýL*éDDDDDzÉJà*`¶»ÿ1édD¤R-""""Ý&ÂRU'¹ûmîžÈútfv~fwëð$Î-"¥£"ZDDDDª�À�À4wÿŽ»·$‘„™Í2³ßz›W%‘ƒˆ”Žž‰‘�¦ø.p«»oI* 3›Ü¼°¤ò‘ÒR-""""Eøp£»¯I* 3›Ü|”T"R*¢EDDDd ¸¸ÎÝ—$•€™�þž°¤è°¤ò‘òR-"""ÒmýÁÚñöak*Ðd°™Ð;Û�*3›£ÝÂ$]q”4¦UÛ Â’T�¼ù°w¿lCœœÊÀÌìÀ�£ÊADz™Šh‘â-^þœt""Ò»TDW�´18¹Ì§ÙÜè¬,ó9DDDD’²š°lÖÏ� ɦ""IP]YÒÀƒe>ÇO€¿)ó9DDDDzÛàËÀ÷ܽÀL«V‰T"Ñ"""""]Û |ø†»ïJ:IžŠh‘ÎZ€ï·¸{ÜÉE¤¨ˆiçÀmÀ î®y^D¤Ñ}ØƤ68Ùa¢CM 69,ntÖ—ð4žëÁñ?Ž+Q.""""Iº¸ÎÝ&�ˆˆô]*¢û¨´ñ%à†ìk#ÜZÒÆOGÁGtZKpªç�'õà´±·9ˆˆˆˆ$éàZw éDD¤ïSÝG9Œ0ØÜç°Ò`—ÃT 3__½^®O6K‘þÉ̪€!À»Üýޤó‘þCEtÕ _[ Ÿu'“»=mÜ < ¼Ñ""""E3³+€[�Åîþž¤ó‘þ%•t’ßgSÇ`!<4GŸhŒèýÌDDDDú'3;ßÌž~ œ”t>"Ò?©'º›c mƒóSðWuÀø:  íW("""R€™Í¾ \œp*+SC«&CÎCDz@=Ñ}ÔãÄ ô��™ IDAT¼¤^}HXÑ’$Iiî' ,Ì M᮹ùCH#ϯªÐlðp=ú—T?Ñ’$I).~cŒ!„7Ôºƒù=H[b]Dù-±:�ï—hyºÖ9Hª/‹hI’$ w+€Ïÿcl¯G9òÕn‰õ+àcZÔ#Iõg-I’¤áj-ðEàË1Æ õè G¾Ú-±n..ÐrW=ò�4p,¢%I’4Ül¾ \c|®„Q�½þ㨟 ò–Xó€K ´\W�<$ <‹hI’$ ]À��OÄ—Ô¥‡Ó^;›ÙWÎaì�ßT¡åbàãÀO ´Äºä"©!,¢%I’4\ \c|°^L>sgfî&7­R³•¤û¯¿S es½r‘Ô8Ñ’$ICЪÿXºKxwh­Ðd'RA×UE¸æ¬*ÚM&-ÒU�剷ÂhÒ–TÛ8sç–ãùUæÕgŽ�Êì«aòY»Tj¶øðÅ-ëë•‹¤Æ³ˆ–$I‚–ö‘»«hvCŒñœÞ…~\Se¼j ãšÇÌ‘? øsOçÆ0ñ™jr꫱Ld·+æ0ýÂÙÊ6Û |¸¢@˳õÈCÒàb-I’$•=k,³>y0;½s/¨²Õs~|¼@Ëâ�ËNR£YDK’$I@óÔÑìú‘ýÙåýûÑ4¡¹RÓëH+nÏ Ô$ "Ñ’$IÑÂØ&vyß¾ìzéŒÚiL¥¦w—h¹e`2“4YDK’$iDjßÜ4ý ³™õ©ƒ³çøJM+Ðò«JMÒ f-I’¤'Gþü×�õùæi£+5{¸ø~�–ÎILÒ g-I’¤#GþÅÀç�“+Ы�ÏÿV eÓ@å&ih°ˆ–$IÒ°—#pðŠ Í6ÿ |¾@ËêILÒ�c-I’¤a+G~àÓÀ›�¦2Í:�ï—hyz€R“4DYDK’$iØÉ‘ßøð^ Ò’Û¿"-¶`@“4äYDK’$iØÈ‘Ÿ|ø0¹BÓ[HÛUÝ5yI>,¢%I’4äåÈ�þ¸ ˜Y¡éü ¸ª@˰™R* BÅ™Sÿ; !ÒýÒm1ÆÞW¬Ra<ÐcŒÝŽsÛáþBcHÛã� ÀqÕ^;¢Šèù¿N~T å¶ù€³�ö-Ÿ+iwðà(`'ÒÔ…k ´|¯‡˜¯."M­ë–·‘ö \–#?øFÖüÝZºº]ÿº,‡_h¹¶LÞÿ¼2{zxŽüÕ%§Y åº¾¼’$ih)Ðò4ð³Fç1ˆø^HuB ¼“:4„0'û~yŒq~Önp8©fjÎŽm‰1>Ý-æ±Àà~`°i±²U1Æ!„c€iÀ<`:°0è !,‹1>˜ë{dçbáq`Q÷B»—××D�ž<ôiÍ©UD“ è·É‘¿xgv|ð9€ù&Ý��¤yÿ§gäÈŸQ å-Å`9òßÞ–=]Iú‡3x铌7�Þãâ5ï!Ú¥rÙùÇ€‹hàL ¸çn%ñ’î’$I’¤Z)­[bÉóB˜œ@*´×kH…í ਴ãÃ%1Æ’F«w!Õ@ŵw6t;$i”y3i«ºIÀž!„VR‘½ °)ësi0sðT5/*Å>ŠT¨/Š1.Ë>4¨ÚH+¢‹® -ªö{à^²äsä_ \ üxc�–Dzã»ׯ͑ÿU�–_åÈŸI* 7gh¹7k»7ðÀ®5Ì÷"à5À×I£Üo-9羇’$I’j&ÆØÜB8˜ ü5Ƹ¼[³ÃHôãÀ‚â@pIq½wá©ã Ý®;x!»n-iˬR£IÓ«WÄca6iûß�VàÎãóY_û{SeMôÜ xjGÔ©Eô"àíZþR<�#ßü ÐFI P å™ù�Þü 89;ýëb�µ]|$G~|­’-в:G¾ø�¯­@ËŠZÅ–$I’¤¾!ìDÉm–ΤŽ1® ![ò|9[þº'Ƹ®äÜ2R=±Ê¼÷"M#_[¦.ŽZOéá\¹kж¹ý5ÛnªûhuÑæìqT¡¹BÌâJ܇‘¦~?�…¦ìþèÒº¸x¬¬‘:Ý“ƒ²Ç£sä—Vh·GŽ|3p=°‘4u`aŽüµÀ�¤½qÖ7UIº²ûŽö&“Ö’hîË‚ ƒQöºÆ›cŒm�ÎG’¤:*®Ú]nà°x|\¡©tßåTîúÒã¡—£²6IkN•ó2 �tëoÙ@JöÌWwôÒ6hY“# ðUàåÀ«³¯ÏçÈÿ øx�–jçåKÒˆ�} |2iñ�R%[¨d( !Lö%Ý/6¦äøfÒ­COõ$IêA±Ž¬¦¸m®Ðn uån‹mbëžØÏR~ä°ˆ.U,xŸ(ÐòÖŠ-3ÙýÏäÈÏ!­à}6i+ª7’ÞÛîqšéå"IÃÜÁ¤úY` iA‘ Á:3›´ÍFi{ŒÍ¤•B'’¶üKÚ&Q’¤á¤8ÒÜýCqºïÈ)k¸ãFྞÎe³ÉÎÊžz9·ˆÞjQö8'G~l�–ª§âhY@Úë»ÙªÝ×/Ï‘Gš¦ØI* 'dÏwDñLf’†²â§¼ bŒÅ݆òÔçÒVƒKbŒ[>$-Y-ô€“1ÆÖF%(IRļ—9?-{ÜPæü�6RëÉbàaÒFáÿ\®QŽüñÙã ÙŠÞÝÝF‘˜Œ*ÐÒ<‘�;²[¬É¤=ʪñLö8³Êö’4¨„Æ�>l+) Ëµm!LÙfŽ5ê¿9„0®Ì¹Q!„>ïªc|,ûêè~œt?U Ýû-IÒp²œô{nz¶R÷Ùïîý²§ÃòöVG53ZºräßÜ|"Ûú»¤ê)À)¤í­¦'ïÏ‘ÿ pc�–U9òÓ€��>œ¸£@Kñ�Ä;H«z!Gþ¤)/>ÈÖ{±{³€ôõÐùwב62=¬4.IƒJa_Ò½ÃcB§—œ¾/Ƹ6k7›´Fq«ŠÎÂóÀC1ÆM%ñ¦ÇëcŒ÷d¿À÷$ýÿúÒ‡™Ç“>)ŸAú�´)„°1‹÷\ai¥Î)lØ<c\]ƒ—ÝAÚërLo %IJbŒí!„G�C€ãB�Ï“ØÜ—4½¨´`ó�e]¢@ËŸsäß|™T0¿«‡f7e�/�F–¿�#ßJúGé—w”\ó༬ýKŽßOúcï¼*r[•#ÿ¯¤Qò¯—œú8°ÃEtÓDÓ¸4 žM?ïÕ„ã¦�m}hmÕ}T·ž±�[߸g¶¨’.¶]T¤ôûB˜Cúäº XIšæ=�4üE!„{bŒk²kš€ñ¤"û¶è�nŸ!;? x©�]—=NŽ!ü•T@�ÊÎ�ˮɅn‰1vîè‹ÍF¼‹#ÛNå–$ ;1ÆÅÙ„±ƒ�9ÝN¯"}(=T×<©h¤Ñ-¤©Õ…r ´üЇøG’n*Úc¼aGr•$I•… ݶ…Ó/%IƒÁÔìqy÷Ù'ÙÏfO§t;ÝÚ½€î~yÇ*­¾9{ìË­ [dÿ1Ùõëq{+I’†�‘6�[’4È„šØZ´–»… x|B™ó}Qœ¦ÕÓÊß½NáêÅA¤Ì6÷ö¶Ïd�Œ !Ì€~$I‰¶Û Û"zdY¼·ÑIhÄ–«1ª.J•+:‹Çëý{k‡@ !ìAšfÝÜcÜØË%µÒDù}:%IR�YD� Z–ßnt’T*Ƹ9„ÐAú�4ŽžG£‹ŸTaÚ'ÙÖ\G�&ÙîmI’4|xO´$i0(ÇÓÊœŸÖ­Ý ‘ÐGa-IÒˆàH´$i0x’´gó~!„¥÷‡¦“¶¤jV4(¿…ögë*áÕoľãÖпí·$IR?XDK’ƒ¥¤­¥¦'†­¤âùÀ¬Í£1ÆöåWÎþ¤ÊÖ{¥Å¹{´ ÆØQ‹cŒKqÍI’Æ"Z’Ôp1ÆBø3p4iuëÒ…²º€¿ÆŸhDnUÚ)û*çÒtoI’4ÄYDK’J+peVàŽ1¶…î&ÐSI‹‰­VÇ»/6¶>‹U®0Ý��ïìáÜêì\O£ÚËI£Ê•ö’.õÐ\E»Á6‚.I’v�E´$i@d÷9¯ê¥MžÏ¾*µë¨+ÆØYî|Œqs…s­¤b¿*1Æ5Õ¶•$IësK’$I’T%‹hI’$I’ªd-I’$IR•,¢%I’$Iª’E´$I’$IUêiuîI!„]<I’F†j¶Ä’$IƒTOEô¾Ù—$I’$I*átnI’$I’ªd-I’$IR•,¢%I’$IªR&7: I’F°Í1ƶj‡ö\T’¤c\×è$$IRÕ¦{4: I’F*§sK’$I’T%‹hI’$I’ªÔÓ>Ñ’$ièè^ht’$ Sc� ¥,¢%IÚZcŒ·4: I’†£ÂÀ¥ÇœÎ-I’$IR•,¢%I’$Iª’E´$I’$IU²ˆ–$I’$©J.,&I’4XÆÐmUXIR]uYW©�E´$IÒàõvà[�NB’F�yÀÑ•8�[’$I’¤*YDK’$I’T%‹hI’$I’ªä=Ñ’$id 4³²gÏÙ\Å5§û÷YXÇì¶ónÞM;íÛL`Âòoð�K«‰÷k~}D -¬¦m_âÖ3¶që·ž±�[ÿØÆí[ÜwòNNåÔjBnbŒ}º@’$5NáH`ÿ’CcŒ74*Ÿa!0¸({ök"+ª¸æ à àv"Õ1·÷Ðma±ñŒ§•ÖÞ®\cœSU!œ\WeFUÇ­glãÖ7n=c·þ±�Û·¸?à\´åWóˆ[ !QÚÀ‘hI’4ÒEز�Ig#‘$ ~Ñ’$id‹´ÿÕè4$ICƒE´$I#U`"0 ¬Vã�f`‘®¬Ý$ ë‰ÄìØX “HGI¼QÀNÀt`#éþâ�=ô[Œ·µ�­çFã€ö¬¸ÝþXz>˜¬VoÉkÇß‹í_㶯k—ìumž©pýf"m=œ Œ:ˆlêW®’¤†²ˆ–$i¤I i�ÕíÌ À`4ðÓì9ÀkIEìØ8˜€»³˜ûsIyi_w·™&]Œ÷`m·^Ìnívl�ÕYÿÍ%׊ŠS¶ªØªTäi‡“Fç7gyÝ•-^&I’F˜VZ?SE³…1Æ9½7ƒÂ9ÀuUv_Œ{_cןÝP�ë{<„â.d!}-¢›úÔz{Ó²Çþ³I`©p…´ETOŠÇ§Ô¤ÏîÒÈs±¸žX©iÕͤ{·!-$Öw’ÞßÝHSàŸ¨I^’$I’¤>éo],2Ûú›Hf|öØQaªrq±¯ 5ê³'Å)RÍ5Š7¤×ÕÞ§+#l[4ÏßfurI’$IÒ€éo]±_±Uõ6f�£Œ.Ó¦Ø×únÇC�r€­z­¦rWЕÝG^½À WräÄl12I’$IÒëo],2wªPôV/�> óéeZ§�§uG�kµ]×DR¡ÞÅö…úމ´Á–}§§öñê‘F²ï'í“=‘´-˜$I’$i€õ·ˆ~XMZuú¸mΦ°uŸè¾X’=¶Ý™sO X–-Þ�=ƒÐm4:0×Çþ�ÉÕpen€ÙcO7Â÷\Xö'­n¾¸—´­Wp�}k˜›$I’$© ý½Mû7ß œ E`wRÁ7Ø…+Òï#ŽXÀT¾¯ùxó³œÇgýF…®jU½>˜GúàH­¤ Æ‘ ;b»ÖiDü4 7g£ôk³÷ûÒk}†¸åqI’$IR�õ tdq¶ó\`çì àQ`9i:rõ‹imÝú àØì«£$×GI£²Åö]îÌÚ¶£ß €=Hû‰õdoR±]ª ¸‘X³û¡‹y>EàaR‘~"[÷×x ؿۧ“¶³z€È³%Ç }¨ðbà÷5ÍS’$I’TVmî#ŽéÒãU¸ª•X‡=—+½ÆÈ"`QgJG××nó|û«+žï›×¶î.þ}¾ê<æUuá^ìu(�÷WÓÖ¸C3n=c·þ±�[߸=Å>Õu%Iöú_D¦’VÒ~*ÛÓ¸x|à`ÒôèÇúÝO#¤×vB¯º/+¢‡ŠmþX|oë˵Çg_½2îÐŒ[ÏØÆ­lãÖ7îÄ–$IÃ@-F¢§ç]V“î_žDÚŠ)we[< EÍô}ìJ ™I’$I’†°ZÑëIûïì”Û< ÜÓmQ¬Á¢3ûŠ[¥å–�HH’$I’4øÕbuîÕ¤º†ŽÈ7:I’$IÒÐS»…Å4øEn!í³-I’†¨½Ù›¶^î”ÍèÑÙú4½º� w½›»«ê»/qëÛ¸õ�[ÏØÆ­lãö-»!—€ º{0ÆøhŸ#I’¤^…Î&”Zc¬z§ˆ‘·9L6Æo¨U~dï¾Õè4$i™Gäèâ“ÂÀ¥ š<%I’$I’†(‹hI’$I’ªd-I’$IR•\XL’$iðºžíׯ‘$ÕÏ ½5°ˆ–$I¬"OO48 IR §sK’$I’T%‹hI’$I’ªd-I’$IR•,¢%I’$Iª’E´$I’$IU²ˆ–$I’$©JÑ’$I’$UÉ}¢{‘ ì \”=ýl!²¹Áù\Ìþµéêãµ³�|]“$õ¤£¹ºÑIH’¤Ú±ˆîÝ~À'³ï¿�+¢s�ý�ÿ"ýܾ¹¹|«ÖyI’Êj‹hí¸\àjà€:wóÊBd}�û�¤aÃ"ºwÏ?̾ooD¹À9À1Àø3“$i$98ªÎ}ø·…$õ�ÿÓìE!òWàm�ê?Àu�ê_’$I’´UÝ‹è¬<8ØØX,~Zˆ,®Q?{§»f‡W·"¿/i·?ðb ×"ºÅÙxEvþç¤Å×.Z ‘Ÿuk{4ðJ`ßì5-®-D­Åk(Db.ðòìé$ൊ-I’$Iê›�‰ü¹Ì¹Oç—"_¨A?ß ¿Ý}4¸ xY!Ò,.öŽ>Эý·€×W"?Ìv¾¬‚­Et.ðàŸ€Ðíú¯æï-D¾]ƒ×@!r}ÖçôZÅ,ñàîÒaÚ‘¶®æâÀ˜¯É ®¦­q‡fÜzÆ6nýc·¾q»ÇžÉ‡Ø�ÏT{©´#ž ý¸~ê?=\’†µ�šÎý0ðKàNài`,©àý8ð¹\à…Èýýìã9à{ÀõÀÒb.‡ŸN>|¬Ù� ü-p ðÿr�Ÿb*"³•¯_<ÆöÅõ¹À[€÷«³ÇÛ�™ÀIÀ�i%m_B…¯Ö-…È�}hß_› ‘ÖÒ!¼ÐQýå­�ݯ/ǸC3n=c·þ±�[߸ÝcGúÐ�´cn*Ä¿Í,x.2*IýR÷"ºÙÞé{r�=€ws¡Et™_(ór�•À�À%mÿ” |•Tð^� K*|¿tÓË*•Åm¢¾\ˆü(ûþqàÎ\à;Àž%m_\܇—r h-I’$IªRCË&»�Fr®C£I÷_w–éãc¤÷à#À‘YNW"wô¾8B1«û‰Bd°¨äÐ÷H£ÞÕZ؇¶’$iÉÞ¼Œ´^ÊLÒ̸@ ðï…Ⱥ¦'Iª�)¢s�Q¤®Ï!-þ5£[“Ñ5êç4Ò"`/#-0V¶�B¤5¸ˆt¿öå¤÷¢|ªŠ®Z€·ÿ� ü¸£§Å ‘El[TK’¤áëcÀ~Ù÷ëH •Lºµì¢\àÄìwIÒÕTïr�i¤-šþx°ø1p%pM û¹¸øÒ=×-À—Ir� ‘{€eë‡ ï/Īö‚þ iÚõFàlÒ>Ò�ä÷çgïèk�$ICÞ/HïL+D¦"S�C�ù¤ÙoÿØÈä$Iý7#Ñ_^JZúí…ÈÃŹÀ߯ío¹ÀK�Ï‘>ñ}ðß…HÌÎíL™B:O/ú'àO½õWH+Ç|>ø)ðàE¤{®�®ÏÞZˆü8ëãcôíæ…È×ûÐ^’$ …È%=›Ÿ |øOàØ�ÏJ’TKQDÏÍ/+- ëÔÇÏ ‘Ÿ÷áº!�ŒŸ ¼&xC÷ý Ë)D–f1þ%qÿ&ðFÒ”ògÍÖ‘¶ÕªÖ†Þ›H’¤Á.»åkÒ½ÑÇg‡÷iXB’¤š¨k� 4±u¥êÕ=4_æº=H£»w"OôÒÕÞÙãš>ôqð>ÒªÚï^GÚú¹À-…X¾ðÍf"Ï—+DÖä_'ч—ÿWÒ”qI’4Ì倫H;yìÓC“0  I’j®®÷D"]lݺê¹ÀtHEh.pé—LOÞüøiUËÞÜ—=¾9Ò*ܹÀè\àô0=;›âý}Òbo/D6"?~OZôìß{éïú\à#Ùès©âÔð{ªÈY’$ #¹ÀnÀíÀÿ¦ß!­Õòrà� LM’TC1�ûÓÀÿo.Ì� Ø�£z¸æ¥Ùã/ ‘gªèã»À‡�ÙÀ‚¬�]� ¤"~ïníÿƒ´=Õ7 ‘[KŽÿðp^.pQ!òÃ2ýM>|&xX DZ�óyÒ–Y5‘ Øò”~z½4—ž-)DN¬U’$i‡½ 8†´óÇ+ që,¼\àÅ ËJ’TSu_�»¹Ž4UúQRѾpi««Ë�[�Åö¹Àà„ìéתìcðàÀfÒÞŒ+€� ò[ÉF¤s�“�éÀÿw‹³˜´¸Ø­À¹À8ÒHø­Àÿ•4} ðÒ‡ûg’ö£þwàˆBdI5yWùÚ"éýY@ZÙóV¶¾g HÓÑ%IRã½${üVi-I^dŸèBäàš\`°©é,9ý›nÍOÏòº«¹»}< œ• Œš»íÁ8·¤ÝŸKŸ÷çjàê’C­ÝÛ">X|ž „âjàõPˆåó•$IƒÆ˜ìq�Î2�‰H’êg@Šè¢Bd}ÍÎÊ«…î¡�Í;r]Ô³€–$ICÆ-¤…Qß“ ,Éž¼ø›Æ¥%Iª¥-¢«t° ¸¦Ñ‰H’$õÁHó>À•ß|x{r’$ÕØ *¢³Õ®—?+DÚ��$IRµ ‘µ¹À1ÀeÀ)@i‘¯À“ÀÓýìæÐ\àýý¸þ”~ö/I#Þ *¢ ‘5¤{¢ÕX«¡êÅўiq‡xÜzÆ6nýc·¾q·‰Ýĸ©°Ýö‡A²¿e>Tæôå5èâøìK’Ô ¸ Û±cŒ�6"ÕW.0¸¹ôØ2>zñ ®ZÔ­éÃ1ÆGª‰B8ŽžPé‰qIÜzÆ6nýc·¾qû;¸¸ªä\[!2®[ì³I[0-Ž1>Ї܎$í Q´1ÆxCµ×khÉ çí@kizVüK’º !QzlP�Dkà­çæÏ÷pøRàsU†øðú*ÛwðÄ­glãÖ?¶që·Þ±%IÒV÷}¢%I’$I.‰–$I¤ ‘£�ƒ$i[ŽDK’$I’T%‹hI’$I’ªd-I’$IR•,¢%I’$Iª’E´$I’$IU²ˆ–$I’$©JÑ’$I’$UÉ"Z’$I’¤*YDK’$I’T¥Q�N@’$I#GŽü,à< ¹Ñ¹ ×hYÒè$$UÏ"Z’$Iu—#? ¸øÀ„§3˜|¸ªÑIHªžE´$I’ê&G~©pþ0½Áé >]œA“E´4”XDK’$©æräGï>ìÖàt¯&NË‘U ¥£Ñ©HªŽE´$I’j&G>o> ìßàt†‚ÑÀ�ÀüF'"©:Ñ’$Iª‰ùWŸŽ,צóùöŽ�…þ}òKw¾¨8úúО¿?£óùöOÓ�­ØqS(4Oý‰ÃŸ:ë�ƒ!^óŒ1ìzÉìüw{Æôºη ´X@KCˆE´$I’ú%GþŤűN-צk]G׆{×ü÷äÓw~ç#/½cC¥x!„²xgôÒõà²ã/C¼¦‰ÍÌ|ÿ~Ìüðþ4OÝKW[<[mCIƒƒE´$I’vHŽüQ¤‘çs˵‰m]qÃ]«o½Çø·>rú++Å ! \\ÐK×O—?Œ1v6:^ÓÄÎïÞ›Y—Ȩ]ÇöÒ•¤¡Î"z„›ÌÙØÀ� º>"„p}•!î^^e[㞸¿&}‚þƒ:Äjï…ï±q»ÛáŸÝî|iß™|°Êˤ¡+GþÒ=ÏoBOmbGdãÝkþÜ4¡ùÍ‹Nû¿Å•â…ö&±Tšÿ¼’T´+ÆØÖðxM�oÞ�Ý>u0cöu×.i¤°ˆávãò;Ÿ‰—ßYz,„08»Ê bŒUýajÜA÷;À¼:Åjï…ï±q»ÇÝ¡Ÿ]Ù¤!/G~6iµíwRîïÈï[óPlëzëÂSo R¼ÂLàcÀ{€1š®¾|9Ƹn0Ä›šŸÅì+ç0îðɺ‘4YDK’$©¢ùéÀ%À?ã˵Ûô—µOt®nû¢—üß͕Ⅶ>LªÐ´ ø&ðÙãsƒ!Þ¤¹;1ûªC˜xRß¶¼Ží]¬ú�¥Ì¸hOš&6÷éZIƒ‹E´$I’z”#?x?ða`Z¹vm ׯØüTëû9óŽk*Å !ŒÞG*ÈwªÐ´“tKŧbŒO†xrSÙí³‡0åì]*tÓƒ«þ4Ï\¶�¶Ç60ýÍ»ÑÒPf-I’¤mäÈ�Þ \Ì*×nó_h{lã%�œyÇ·9¸|¼Â(ào�O»Wè:¿$­�½p0Ä{ÐDf_1‡i¯�]æîïòÖݸ’e—Îgcá…¾](iP³ˆ–$I9òMÀ›H‹†í[®]Çó›W/ÿÌ#O=÷o‹ÇÎxN œS¦éÝÀ×€}H‹x½¯B÷G'£�χP¶b½¸±Þñ¦œ3s§Ý¿xèëÇ:ù B߆Ž7Þ³†e—ÌgÝMegŒKÂ,¢%I’DŽüyÀ•ÀåÚÄÍ]«–_ñȪŸ{ô ØÞ5½BÛù¤Ñß_eÏξ¶B8‘´"þ齤Ø=æ½õˆ—#¿p)ðÀ¸^bl£mÑ–]¶€5¿XÖ—Ë$ 1Ñ’$I#XŽüi¤¢ó”rmbg\÷Ü7žxrÙ¥óíÚØYéÞ㥤­ ~Ti¿eزçò•À«zI±ª˜ý�—#?‰´�؇€)½ÄØFû3­,ÿÔ"V}w)±#öåRIC�E´$IÒ”# i�ärS±!Òºú§O?úäû<´suû¡ÂUµß2lÙsùSÀ[©ÝÎ;/G~ ð^à£ÀÌJ¹o'²vÙÇLYùµÇéÚXñ3IÈE´$IÒ’# ðàBÊ/•Õ¾öúg,}Ǽ9íËZ¯®ªý–aðí �#ßL*¼/ö®”{Z�¯?ý¡‡¿ûì—ŸßÇk% qÑ’$I#@ŽüîÀ'€·SþoÀ¸á®Õ/yËýûµ=º¡ì½ÑT¹ß2 Î=¡sä/®*�®÷¤øðÉ-O†/‡²+—K¾,¢%I’úá^¹_M×Gxê~ZÎht>ÝåÈÏ í{ü�TX(«uÁúùKÞ\˜½±ðB¥‘çNà‡Àå•ö[†-{.ÿi‘®Þöpî5f-âåÈŸAºÿû„J¹—ñ[à£Zþº×JF,¢%I’ú¡‹¦1À^@WñØ…\Øüm_hgìâ¿7t^9òÙºPÖÔríÚŸn}tÉ[îæçé%dq¿å•…šÙºçòý�Y‹x9òÇ“î‡~i/×÷äOÀ%ZîØ�k% CÑ’$I5ö8�7f¿`­¬ˆÎÊú;Òý»–k×ù|û’¥ïž7jÍ/Ÿ9 —�×K€¹!„¹eÚÝFš&}P/1¿é%f¿ãM}Õ¬Y³¯œ“wèäcz¹¾'—h¹v®•4ŒYDK’$ 9òMÀ[H+UïS®]׆ÎeaLøÈÃûýñ;_h¯´Ó\àã@8ºB»{I+[/–¿¯"ÞÒóºÄ³×xf}ò f\´'¡¹ÜÚie-!Ý;þã-]½5–4òXDK’¤Aé.ßʆ™�ŒÝ4�_= ápò3"·2òÉ-Îqœ5ƉvÑôTÍóŠ×twù}›‡6Á´H| :þZàWn=ÿê™Í´�o¢yå½´l,½ö@Î;™0k´Ý͵Ë{Š8NÇž»¦[sØ+Ç+6Tº®?räÏ'í‘|X¹6]›:Ÿoßü±¦‰ÍWhé`MÏíB'�îîíþӿ,9¶]Ô�Œ7j—1ìúÑÙå½ûÆVÚíªGÏ‘ÞÃoh©¸M—¤‘Í"Z’$ Jmlz)4ÿ¢‰ŽßŸÂ…¶Ñúéc8ïMw:†»–B8šW~(0þ“À("Ít¶Ëù—ÝÇo¿^ŒwçNÙÌèk† !†­C°£:sœ÷‡¿;`ß‹4¿´‹øàšÒœ¦2æè.ºnmOSœOê)ï1´]ØEüfñy<Ít¤QÖÕê=Ê‘ŸK*({Ì ¶umdT¸¢i|óW ´l*×.„p(iêô½tû$i[¨ÆËnŽ<�ñš&�b×ÞŸ™Ü�¦É}þóvðeà‹ZÖöõbI#�E´$IÔìÙJÛÀ!@̾8†W^ áÍ@øV€§#ñâ_<–óVÞÇï~ÐΨ«!æ!̇®_BXàȯ€ð²Zå ñ{¤­¤~ éÛ®%µè#GþXÒBYeóŽq3]ñ+alÓç ´”w†ÂÞ¤"öo€J÷+³>¿c,;R;�ñÂØ&vùû}Øõ£2jçJ[Eo/¶wñÂïV<2íÕ»½¸@ËŠ>],iD³ˆ–$IƒÝˆ ñuíÄÛç0aí/8,Ãù§›!þ©‰gν—{Û³ö7Ë+ÿiº/>øÔ/Žá®�`Tèh¢éµ÷òÛÇŠÁ�ã¸�v2û]µJö~~sÇqwO³ßм—ßnìíºjäÈ |x-Ðó;�±³ks×w›Æ7²ÀïÊN!Ì$->ö RºøðåãºÁ/4f\´'³.?ˆ1{ޝÐU"¬þùÓzZWÍêv¸™´2e5Æ…^Ue[ãž¸Ï‡Ž£÷ý6w$öP{/|��ÛÝÿìš7˜VåuªR„g»ÐϾ=þÎßnõè@ ÐĨýîå·7çÈ/öCÛ59ÎûÂ8Æßqÿ]öáAæà�½¶j »�îï-kôîã˜ýÙÞ¶„NM�|ö5ãUmã=kXvÉ|ÖÝô\MãVé>à’ãzúà@ÒÐb=­çæÏ÷pøÒã>Õ\Bøðë*»3î ‰[ûõµŽ=ÔÞ ßcãVˆÝçŸ].p i‘'ÕÙΟ<vðå’Û¤·Ó ;gß^ áß Î…0·•Ööù{ñË÷ñ»ß @ÚýÑ祦m‹6°ìcóYsÍ3�èþà2à1ÆJ[‰Ib,¢%IÒ�ÖDÓñíÄÍåη¥½†)ÐòËyõ-t^‰g’*;9~‘#}�–jg64ÂÊÞ›¨¨ý™V–j«¾»”Ø1àõërÒ^ÝWÇ;ºsIõg-I’†œüv]Žür`V$nšÇo­æº»øÕ*à#'òê�Úéxð àœã¸`ÿ{ùõc] }[vÚz^L5×ùB;+>ÿ+¿ö8]Ëî–U//_¾c¬ÉBr’'§I’¤!)ÀCÞ°#×ßůVhùp;@]/ÉN-Ëgv¿&ÒUUA|÷uí�„±;’Ÿª×ÕÚų_|Œ‡÷û#+®zd  è6Ò}èûǯ´€–†?G¢%IÒ�húL¤ëLàCÇrÞ¼î÷5Çkvëbó£h¾¼�xd žÐIÛ¿ÌㆠÅ6§sú(˜œ­”KqãBâ«�ã¸oWþÎqÞɾSMn‘�%ÿD„Ç2úT æ÷\·Î_ÿã®��e·ˆêÚÔ9vým«Î M¡½\›¢ñGM¹cÔ̱Ï÷Úi ®»qåyÕäW똣÷7fÆ›÷ص§sÏÿðIžþð_«I«fBS�]€ƒbŒK´sI e-I’†¤{ùÍ]9Îÿ ÄFÂ/Ž!0/@'pðb£ºèúéož‹›ûÞç_q>„Ñ0ù\à(àöw;@;Mÿ5šøQ't1ûžù á êå�cZøî#‘ð½çÿ/Äö@w¿}K-^ÿ¸C&}¼@Ëå·fo¯2ÜÏbŒ¿é­Qóïÿ{wU}õqüsf²�DD·Š‚¨ d‚Š VÚºÔÊàÒR­­ÝµícŸ.îV«E­µ¶ÚZ­µËS»ˆu!T«­-j]�&6÷ d‡�Lîyþ¸wp“d€L&Ë÷ýzå•äÞßœ{frçÜû[Šs‡ñÙéŒ|'Í/3+Î..v÷›‹œ’ˆt2Ñ"""Òm¥˜qa‚ä ~Lˆ¾2^püŽå¬}{ ýÿ þààÑÌëÐlp[9—Lgz3Àf¼UMòsŽÿl$0¼ ø)ðwàÁ|rk¤âGe4|˜š9¦ã³;âyKç3³p&á¤a{9)"Ñ"""Ò%Eݳ+Úk—¢æïÀßG1µ_ #€¾1b/ÏáÞW³š½œm\þõƒ™?4FzoÇW–À‹µÔl1†µŽš¿�#ùïcÄKŒ .«Ýf9ÕR³8Wž ™¾8sSÏÝ@þNóªìð|þ¯€tf6™pùºƒŠ�‹ˆŸŠh颢5ÕVçûðZôÕ¦¨h~r{óz‚éïí� –.ÇÌŽ®&;é:TD‹ˆˆˆˆd1³QÀ4 ¯IÔD¤wQ-""""˜Ù^À€Ï ¥`E¤*¢EDDD¤W3³A„³mŸh]oi“Šhé­bfvð= ±“‘îAE´ˆˆˆˆô6iàV`>ðË"ç""ÝŒŠè^n7® ÍòͶ•3üô„16ŸÇç߇7ñv^ÇRÜ®·�±·ð±·°q·3öùCDŠÂ�éÀÅî¾ÔÌN+vB"Òý¨ˆîå*ùH®ÍG_íêǤ­9œâv‘¸…Œ­¸…�­¸…�[èØ"R4ç»{]±‘îM³ŠˆˆˆHOVãîÇ»€6³>¾1ÐÄe"ÝœŠh鉖Ÿq÷‡Š™ˆ™ÅÍìKÀ’ ¡¹¢˜¹ˆÈöSwîÞe5ðT±“éE6;‘^hp9ðkwO;3;ø!0²Ø¹ˆHÇPÝ‹¤œ0¾ØyˆˆˆˆÀ*àZàzw__ìdÌìhàjà°"§""LE´ˆˆˆˆtkåûô�|ÈÝ—·Û¸ÀÌl añ||±s‘ÂИhéÖ*�üd± h3ÛÇÌnêQ-Ò£éN´ˆˆˆH7ôÌÈY÷سÖÖ¸{î~šG¸ÓÍìü<Ú=žO~sà'‡ŽnÍóØ�¡xÓÌv.¾ ”7%é *¢EDDDº¡`c06�f+Ü}v{�¢b7Ÿ±»+ó‰Wˆ˜ ’ù¶³Ü \¼¼ô+n:"Ò™Ô�[DDDD$?ãÝýTw_ ”£Z¤×Ñ�h‘¶ÍÎw÷Š�ˆˆŸîD‹ˆˆˆˆäö"ði J´ˆdèN´ˆˆˆˆÈæÞ®nq÷Æb'#"]‹Šh‘Ðà:à:w_[ìdD¤kR-""""½]#p p…»¿SìdD¤kS-""""½Uü¸ÄÝ_,v2"Ò=¨ˆ‘Þj†»O/v"Ò½hvnéUÌl ™]ü²Ø¹ˆH÷£"ZDDDDz3ëcfç/ßú9%é†Ô�[DDDDz43‹Ÿ.v/b*n¥±4PZÄDd;éN´ˆˆˆˆôXfv °¸•âЇÄúÆ×1é*¢EDDD¤Ç1³£Íl6p0²ˆ©ÔǸû1î^WÄf˜;^ìfv;POq èeÀYÀ�î>]´HÏ£"ZD¤u�_¦¡ªØ‰Dj fu@œ[ –í_ÉçŽé,']§å�ð£8~O´p”Ã/Nu¸øzü¼Ø‰‰ô«|{"°8°"¥±ŠðÎó¾î~³»§‹”‡ˆ˜ºs‹H�g† #JaY®î¾Ã�òJØ7/Õ:yOø2ÎÐ{– [ó¸ÑÆ�ô�缘+×10X×r_½ó³\ñ2�Y//q6L2JVÃðþðâ,§!Ó6aœàðÁYÎfð5vMÃNͰz>¼ž]`ÏvVWçü©Ê¸®ÞYÒÞóeôë »|^žî4g¶f JÃ.ëÀë-óȈ^ß]çÃw‚öŽ—y-ÆÂ~Íðî|ßò.{æu‰ÃŠ9βöâ%ŒŸÅàºZç•¶Ú5‚ ]ï4E›f$Œý��›ñyuï–B¨ ã‡;b0¼Ž�¶=Ü 'Îó°`®2Ž®‚?C£‡=“£Êø�ÁÈ”3aŒ±G–:ü¸*¼óò�Jh¨6NrèüØq¼]mœPçÔ8œc0/å<š‰k†UÁ›À€8PÏUŸ®wþ›i7w ¾ |§µçz¨1, ·–ÁÑiˆ-…u ã')çÒ*ã?GdÚ®‚7Æ×RNMôš‘^ME´ˆôt› ½RX–,'¸°¼ß­9ò(ð�t8.y‹îè1¨rh*…[›w"Ù“°r^¹8,‹rÁ á°`–²Út—ÓauŽý«3ÇÂn‡8[v‹gŒÂníª‚=ˆÎéðn~cVÓ—³b½Ñ2ŽÃXà•¬€Ì˜è¨ú÷€c€aÝY7è“Õ|³×Éà-²º€·ÆØbÜq-°ï!°GvN�Æ„gìôgìÜN—næ:s€“ÆQ5ÀãŒj�ÅÙíª�ñ„=n«wîl/®ˆt}f6¸ 8¨Ø¹ˆHñõÈ"zãJ† …’¦36}ÈÃ)»ìÄŠ÷f1+ý~Ûdß4>*Ž`ï”bóçp﫹㞺[š¦Ñql'x3€Ås™ùzfÿ¦îÔÀºÊ2|ÅlîßìÃìT¦Æ—²n�f*šçqWÎq¢û0©b }‡@ˆÓ´ؽc_'¾›ßEÉxéôº5¯L�ε¯¥& üÉ®—Œøg>m·°q »­¸Ct}Fw¹%„[Õ/•~0š™µk€ÁË9îlƒçú5Âí¹&“ÚV¯äÙey›¼ã@,¼ Ÿ]ðŸ¬�Áñ™.ÚUÆèVÖˆÉLzöv®�'–}ÛáèB,èu©>Úá uΪ„ñ¨ÃwÆwewÝ®2>VïÜ×Fœé18x0ʼ7-E…öCÀœFø” h‘îÉÌö~@xÑ,VätD¤‹êÑE40ÄÂÂùóMØ¿Róö'øDìPNfľëð\#2=s7iÖaœrOéÅÀy˜zë“´² �&�±/¦˜ñþ²0ØÅc™|zœ’Ö>Ln•yÌxøz‚ä ÀŽM«çÞEí=ND¶Ù¹À�ðr•q�…t¥ÁÉ™•ÆL‡Vã Ô;Éjã<‡'àÈj£Öa`ßð¢Úíl9Æ:/i¸¥¾Àª�YÀ`‹–œê(‹�5 ã àãÀï3Û=ÌûK«an˜ã0Ñ ¢•0§Z8ózNõÎ ã‡ï/…ÏV�NxçûsÀåÿI„ÏqŒ…c•;„CMµñ‡GÕ‡Ï ƒ¯9ü3€E ãŸÀƒ#,ìáSÙZÌ ì"?n®SßÖ±-,´÷4XUsZŒ—¿-åܸ}ÏND ÉÌζ}­ WÉèéEô[i‚Ä|þ–}—¸9Áäoeà?È* xŠ»—'˜r?ø'Ø8jRõDޔмY×D‡þ©ÐOBD¶ž…KD]¾6õâˆÁ:gU} å<9ÎHx¸LÔ·Ç᷵΂Ìc*à ð�Á1™Y­ëœŸV'Þ™]epî:økkù8L‹î6gçø§ =ßY1Ö8ÔÂÃ÷îŒÁ>'œi;wM �¹é¹4óq¸¼aóž1�›�)þ³Á chÊÉÁRΣ ãXà+@ƒ‹,œ)û”’pü7cŒQq8$hgÆð”ó?c�ûbpœÁîˆÃíµÎKUÆDƒÿú;Ü܃ÏÙû“”ýš-{ö<äá:Ö­yÅàòê�Éöàù–Á/ÜÃHuÎ3#�úÀbp�C¥ÃíáÌæ­©w>ÙÖþŒ8ü'ú·ÕbÑBð÷g‘®'ffNxØ}Æ$‰HQõè"Ú`u‹:³gTôÃÈ*¦|¹åÞ>(õ컘™�&˜üØ‘ib¬"yõìß‹™Q¨îÕ"ÒRÎ"Z{µÎúÛž'üð”ÓÎà/Ñ×&õÎ\`n¾ùÔ;WåÈñÏÙ¿Ïu^',0³mʷι'óólg5-žË\ç¹–Û¢îå›îŽÇà×Íá°•ë€Ó³Ú=DØ9Ûüì_Jà‡9õž=wCnјá-Æ G¯Û™-6_–µÿ×-ÓJn›Ô:¯dŘÑZ»ÅÎàgm¤½Í¢nÞ9»z‹H—”n%ü;÷Ë"ç""ÝL�.¢Û°øÍ.¶-ç Ú´%ˆºñ8Áo�*ƒ;úâAÉù†ßTϸÛ�ï[é‚j�¦„ñà £¦e!ßš„ñm`b,ìš-"Ò]9a”‹Ý}©™�Vì„D¤ûéÕ&�lÆÆ´öÕDÓmuÜûŸf6Ž»’°;g`0ìWUÔ=5‰I›]Œp¼ó䈈tˆ”ó/àë“3µ×þPcO‡ƒ ¾Xëa—v‘nè!àw?ÍÝ·kIÁmef}|c 1×"Ý\o½ý0>F,Hqï³í¶æñà:àJàÊQLíWÆÆÏ€_Œ^MåQÀ¿¢¥¯:ä�c@ b\d�bj¿ ‚ZjÖwdÜ LíóK 1¤c<'ô_I©wÆp‘”çî¾XÅÉ;6Qº~!Ó7Í.=Çyød^WâµL_Uè·Õ$&Ul OÙlî_ÝѱG2¥rGš,ŸØÝáµé%ê€óݽÕa!…ffqàóÀeACsE¼¼WßÇéözåÿ`dž?5ŸÞvËÜ2}mŠ¿4üî0GGߣ‰q|ðv¦¸ D³CŠl³2îkÆnïè¸ løy_üq€1œ°G‚äïª9ñ¸í�[Åä)YÙ>½ýYn›ñœÐßh^QΆ3æÄ�c˜²v›2NhXQÍä¼&Ü*†•T^ÕHÉ|€Ã8eP‚äïªHž´½qL9ª/¾¢‰Ò¯åÓ¾Œ†É +ª˜|Æö[D¶Éà“„wŸ‹Y@Ÿ,$ƒ½{±ò‘ŽÓ+‹h#}-°ìS¹&«â䫘|M'MH0yL‚äM˜ºSËvŽ Š¾?#ölø;Ï ›fx¬æÄšˆß™~¼ÀÄ­~r"Òi ëk0> ¶ÛöÄGrgÃ~gø )ft™ nJ‰}6Ž/ÎÞ–¢æàW;vó8NÝ®çÝšh.7ÃöØž8#™RI8[úÿÕ1ãGù<&EÍŸ�+ »i,“õÁY¤ó,Ît÷éî¾å8�ÀÌŽ6³ÙÀ]ÀÈbä "…Ñ+»s§¸ïåS.¿Îð«H~1uk ö78¬ø}ÑC¾ÐÀÆOU“|ÐñŽaŽžÛ.;C9åo á5°½‰ÿ7Aò)`8ÄÆ,Ï7?Çîÿ0-Á” à«€a)jŽíØWBD¶Ç\î{Žø`Ô 4n¤ÏÅ�VÁ `íVQùñ€ô¥„T»¬3Þ Þ£àÛŽW”QòÝ­y\#W–Ñð‰v°ÅE[éP«€k�ëݽC‡òl 3\ _¬D¤°ze �bÆ/ÇqR* ø©Á‡±°ifî7�Û*(_ØÈŠæf*~fp†Ã°)Y•ïŽÁù™ñ‹O0}Ã8N:# ø-Ø0`o pøs€]Ççå“Û~”ÿ~ £ ¾~2ïç$ÒãT1峆ŸCØÅÍ�¥L›KÍ?Lùø‡cÄnh¾l Xʉ}­ž{^ÊÄI�üa‘²+ø“`»ÓÆÅ«j¦ïpn û®üÀÃY§_ˆÁ×k©Ù4yVS>aøw€}�y`•™}ã˜: `ã\3—³ªI~Õ± à!\3xˆÁíuÔ\TÍäs; ègøµuÌüdºP—œmØ¥ÙëÖW‘üðÁ&*Î*cã· ]GÍÔhßÙŸ†/8~Y=3ï_‡�í ñiÀ‡ ó»>ÅŒG«HÞbðtŠšÞ&ŸãØA)jÎnñ¾|Ìà«Ñ{ð@¸5øGŠ™?™Å¬†j’×9~ã8’—ÔRón>ïõÖJ0å(àÂ.ˆã8 ¼êÄέçÞ'6ÏÕ/F®ÏìÇWK–Ít¸±ž÷%H~,é¿0ì*°=ÿk=5ߊþ­}ØÉ°Ÿgî8�bjYþ-‡ŸâîåYÇ�hø�ÑZ×·ÔQsK¦ÍB¦7&Hþ¸y4S.^ÀŒ· ñZ‰ôvåûô�|ÈÝó¾iÑÑÌlà àSl¹d¼ˆô =²ˆ®åÞ@Eíž߇Iƒ0<Àƈ½^Ë=Ï·hzpÞh¦ )%æÊñrM,S˽O�bêØrÖ�†Ø€RÒµYí6ËéEf5äÊs:Ó›�ÿÏ —5R:"À7¤©X’çÓéV ßü)ÇêÀ› ûl fT3et3–:Áþ†M >ìØŸ 8šÿ@XT‘`òÅ`W8<Ã~î°Ñ\­ ö4ì¸?¸¸Þá+Ü=Š©#ÂâgÊi†ÿX`ØeN°#a!¼<Œ±¶ JŽ3ø¿ðwF~:a/•ÿÛÅñ L9làvðû1ÁäÇRÌœ×DéGÀK› þ¸ùëÂHƒ)e4ŒÊ šäµŸ»‚—;Ѱ™ãHÿñŸìëØ†›ÃG¦�Ù²O£c‘cèH4ÇÃRƒ‘àGÛ6uí.¡ä¯M¤otìøðymiSû4°á³m½%Äï›Ã½¯æÚg»9v\ Æ;~—»ÞñÏÁ=c8nØ<\Ð3 ž °+€¾†�èšè*^�URrœá÷Fa÷?Ù°Ãþ/FÐìL©6|`€ý |,ø5ULy¼ž�—ÑxÐ?ÀþÐâ=ÚӰלà/+ öaÇo®fÊ;u̸ûý×2~—Ñ|SÁ ÀoÛz=D¶Æ3#gÝcÏÚÆ6špðÓ<Â�nfççÑî)ò_ê®CcüäÐQ„㊷Pyìà'‹U@›Ù.Àńˊ‘ƒˆt®YDo­¨˜]Ð^»èB»w¢Ymë¶7¯¨ø®ÝÞ8"]YŠšK³ÅÔe4¬‚àH ³ɺfJžÇÝoT“\üdSû¬fuŸ2ÊÎ3¸)EÍ9™8 ’ò9~GÍ¥æé0îäEŽÕTи¿a «˜|5؃)flê’—`ò>`‡´rUš 1Ÿ¿­ÛOùx,Ænãk¹¥)œÝ¿a•c�yÁ–Î¥æõ–�JÀ¾�bF À¡œ8̉�ëØ õÌøgÔì_ ’ãüœC9é2 épFŠ™õŸo«âäóy-²ÍeF}5É9œ˜¢æê–ûŸâîå ’Ï8>�VŠèfÒ}ÁÚœ„« æ9‹è ƒãë˜9 &ópŒ>c€' ¿üÉ; Ç=V‘ÜÅàä6Bn„tu=÷¿ÅœTögÍ!³˜ÕÞ½~s²áG�;Í [6�›­æ�bÆ_€¿dmº£šäáÇ ó¨çž• ¦,;ÑÒ�‚�ÁØ<š­p÷Ùí5ŠŠÝÃòˆ·2Ÿx…ˆ™ ™Ïa;S3°xèWä\D¤©ˆ‘¢Gòˆ>f0º †æÄ²'ó[Ÿ) xÁ€4�;—Q±?ýìþm9vI4‘@3öB Hã;�夽¡yo‡ë·2äºLy x¯–[š œÝ¿šä:Ãv0â¯äeË34@šØD npp5Ƀ²Æ0d¼¹8/Áä&h~$ÅýïÔsÏÊ­|ùz™ðýÊ)êúœÏ‡ü6Åi~)ós ±Ò@0ø0N±K3tž6¦¢Àà-ð¾³Â ªÔrKS‚ä{`ƒÃý6Èù�#92€“ óðµæø¢–í^� Õ×JDº�»�‹€•¨€éuTD‹HQU‘ü‰Á¹À߀' ‹•œÝõ2 !\K=ìº ñÿno.†7‚ÇÍIïö„´íŒëÍ[l�fÛô³÷òSì{ƒ9ø!-*ÆÁ_®å–¦*Nšl7ƒÝ %T“¼×i>7Å}/oßóÈ‘ ¼køž­íÇWK�7«ÛŠaT<³5ë(oÀK£‡6’f€Ñ<'ßÇçâx³m9|±Ùq s´þŽoñ%˜|&Ø­õ`ÿfZ8vz 1x×±áÛ“§ˆt ®9ý€™íZÜtD¤TD÷ro\øÌQvUÎÀÓÀc„WXóQnfù®ÃºHq ·(ï]|ÇÒ‰ÀÎyÆ Á”¡ßûNŠ×½¿=¹Ë<Ù;à”’¼Ýnó|£{Çq <Ñ^ûíðØòˉ×bi‚³ZÜíÞ$špëàq$G:–¿â—NDæÙ“nm¯ð.º½×Úþ�,Û©4¼0Ò†�ض âï@3Nlðà¶ÅhŸãï¸å›æpo=5›ÖÌ®&™s�oÇwZ}­D¤Ë›GXßnmðmðÇ .ÿoxÜ~�aûØÛáï¾Øl†Y‡¹ [l{,À_XMð0°‚37ÏÏž7|‹®Ð)j¾éðEƒ†]ãp>ø*°J(™ÜAXßnØ9À�û³æûáó)¿ÑáVð/ö…p=k¿¼ MIxıe™÷'Z²kpËûeòHœa°¶™†VïDo¯æðu}d=Í›>ÀZøó#ñhù¾T\êø�À÷3ø®Ã|—%œ 0€=à‘èýÆÂ×ý±ìã¶0€¹ÙÛ<|üR€zÆ=¼éa‘œÕ&v¦…]›wÑí’R̼¸¹ØyC=3—#¶¢ý­´³æ¹ˆt.3ë|8ظ£¸‰Hw¤"ZDDDDz43‹Oë æžIDATŸ'œ4l÷"¦âVK¥í¶‘.KݹEDDD¤Ç2³SWI¸•âЇÄúÆÛ� NDº6Ñ""""Òã˜ÙÑf6¸ YÄTê€cÜýw¯+b"ÒAÔ�[DDDDz 3\ {™Å%ÀÅÀ�îîEÎED:�ŠhéöÌlà àS„KWË2àrà×îž.b"R *¢EDDD¤[[ýàÛ�Å@YÓX\ \ïî÷,Òƒ©ˆé†*�Ùù”å·¾òfMú×Fã‚Ûs#aèöxžñ:"Òy4;·ˆˆˆˆHžÌ¬ÜÌþx˜¨Z¤÷Ñ�h‘v˜Y 8“pÒ°½ŠœŽˆ‘Šè^®ß¤Aç­›½â¹»žÞÊP7÷æÙvkb+naã:¶ˆˆH·gf“�«€ƒŠ�‹ˆŸŠè^nè´]6mIÎñJfv70!ÏPÿãîwäÓÐÌî63Å-PÜLl:ù½ñØÄ�;LÜ)ßEDDº<3;‚pr´‰ÅÎEDºÑÒ–�€!y¶í£¸]&n!c·×Ó¾aDDDº.3L&;ézTD‹ˆˆˆˆf¶ðà3h^i…ŠhéÕÌlp!pP^ˆc$Hö¾ô+D|é<*¢EDDD¤·Š™ÙEÀ÷€þ…8@‚d9pp°s!Ž!"�KE´ˆˆˆˆô6iàV`>ðËB A2|š°{øÞm4m,ÄñE¤p4ÖCDDDDz îp÷³�•…8H‚ä‰À\à÷´^@¿|¸¦9ˆHáèN´ˆˆˆˆôç»{]¡� 9�pI¬#ÛhöášÓ?OQÓP¨\D¤pTD‹ˆˆˆHOVGXð!w_^ˆøVb úâ^ìúý”îVÑVÓzÂâù…ÈCDºÑ""""ÝÐ3#gÝcÏÚÆ6šárN?Í#Üéfv~ížÏ'¿ŽŽ9ð“CG®í¼…Êc?Y�Ú`Ç�eè•#)±C[-Ÿ.þ’¢¦ ã¯E¤ëP-"""Ò ƒ±y4[áî³Ûk»‡åoe>ñ 3A2ŸÃv˜ÊïÌЫ ï¸Ûjöpð«5M�“™ˆ›Šh‘HßĆ^u•Çn«ÙÂñ×?IQ³®s2‘®BE´ˆˆˆˆôzåûîÀnWîÏÀOîv„Ï­¸ øaŠšw;-9éRTD÷ro\øÌQv•íšc×ÓÀcÀÊ›gó ÜýlÅ-hÜNï$>˜ïqEDDŠ)A²ÿð‡'Lí;nGb;ÄÛjzpAŠš�”šˆtq*¢EDDD¤×H�,Î.ê÷ÁAmõ z8/EÍ:'3é.TD‹ˆˆˆH�— > üØ»�¦‹€ SÔÌè”ÄD¤ÛQ-""""=Z‚ä‰À4`tÍ^.~Ÿ¢¦¹3ò‘îIE´ˆˆˆˆôH ’€«�#Ûhöpðó5 �’˜ˆtk*¢EDDD¤GI�<�ðÎó”6š­~\“¢fU§$&"=‚Šhétff@_ lt÷�ENIDz€É=�Ë�3 ÿ¾ä’n.OQófgå&ÒÝ™YЇðÿÐwŠœRѨˆ‘NÏÀýÈú€kftד±™õ†^¨š€uÀkº8 Òi&Hþ8‡ðÿakþ \”¢æ¹ÎIK¤û3³„ó ôÏÚ¼x¡8m3ìÐN³·Ú:‡«ˆ‘δ°ôóªè«/àEËh;˜Ù¾¼ÿ|ZnfóÜ]wºD oj;ûÿ œŸ¢æ¿�‘ŒHOaf%À!@мíêΉ÷vo§ÍZÚxŽ*¢ED¤3 ‹¾§Ü}YQ3ée„wž_#ü`±‘ð.Ø0`0ÆÌVºû†â¥(Ò«ÕÏÿ(v""ÝÔ„çºåÀwï–½[ñ.°¢•}mž·UD‹ˆH§0³ïßu~·Èét”·€%îžÎÚ¶ÆÌ–“€r`'àõb$'=Û°{!ØØöJLñÊ’ñ ’³Û‹uÀ¢I#šW7µ{Ì|ã(f¿|Žy¸¸#EMOúÐ/ÒÙ2ÿïÞéa4À»î¾M]ÒUD‹ˆHÁ™YÂ;³Fx·v×px4o»{cÔ.ìJ8°;Õrw_Ù"^° ÐäîoEÛvˆ—)Їiw_ff;FíË —³YæîÍÑãvgö¹ûkù>/w¯•í�™­�b–åOdkôÛ¿ýF08¬½Fä]Ÿæ¯P1óððàÖ5íWð"’St> ìm*7³=¢Ÿ×gŸÿ¢1Óƒ€JÂsüjÂ1Å›]å3³]ωï¸ûƨ«ø@ÂsùJ3Ëœ‹ßÂÏ;Î3ò–»¯�âTžÓ ÀËîÞ©ËÓ©ˆ‘Î0ý\œµï  ÑÌúcÙ|âÌìEàÙ¬ÉÇúF1Ö˜Ùªèq;Eûžš£ý룓û¾Yáö›Ù<à `¯¬}{˜Ù wŸ·ÍÏ”McÈ2Ï#g¡-"j5ðcà')jÖ;‘ „ÍÏÕûdýüð^4Yè6?Çf¬5³zw_“µm?¢ø)3Û=zl,Š77г°„ð\�=Ià03{<ÚV ”fíÛÓÌžp÷õ[ÿ4·�Šhé ËÇ}:kߺèŠ÷!„Kg¼ ¼Hx5{Çè1ûÆ-gÔ­Ž$<™f&IÞñ†°Ø¼J8f¹Ixu½t¼ÇDõ#<¡ïnf/¹û6­] 8ˆð»t[ãˆH^š€_?LQÓS†‰ˆtiÂÂvÂÞ"/N áë½ûž{+ ϳû»‡˜Ù#-ïHÎôÝ—ðÿxà /Œ=ÅÞ›ðN÷¡„çý„äš óÊè1[{¼ot÷;gC¾]ÖUD‹ˆHÁ¹û3k&,ˆw#{4ËuÂ.^õY»6˜ÙÂBy3{¹Å’¥„Ý7ŸÎîÊu‡ðÄXÛ¢ÛYáI¿x"ë*ù[Ñãv#¼¾Uů™�%¼ãÖ�»ûê­‰!҆׀;Š�DòQÂ^ŸKQóH±“éi¢ž_o˜ÙÐhÓŠì Aͬœð\êÀS™®Ö„rÕG=² ,†³Å�ºÌp¬^!<¯{t¬•À‡ ïçÜ}iV Àá¼ßmkìÅæ½ÑÖ™Ùsù¬ªaÀÉ-¶-ÈNLzŽÉ£�YÅÎCz�SÔ_ì$¤ëˆÖS>Øèîÿj±ïÂ;Ãÿu÷wr<ö“ñw5ã<Xëî�æhß8Xçî�´Ø·;0†pB‘9-öíGx7úewϾ[žÏóåXJø¡b9ðL‹®l˜Ùq„2^t÷¹[qœƒÙ¼ëÜzwpkréî$÷HQ“÷ü"²mÌláøãT‹"zOÂ;Êï¸ûKÇEwxV»ûcѶ DݹÝ}yŽÇŒ',†ç¸û»-öKxñûѬ‚3+Ž!<ï>�Ï�d3Ix÷º�ðb{)á�î>Q“Í üè³Áèìº-""]ÁÑ÷5­ì_MX öm±}[f Ít+‹µ±/¾µA3…ptõ}oÂõ£7³G;{‘žN´HÑeÎÇ­õ¸Êlß!Ǿm9w§ kW˱�h{<ë÷V¹ûâ–Û¢ae£ ×�MØË­U¹>@ˆˆˆtšh¦íLѺ±•f™í}ZÙ¿5Ú:yo÷òîžv÷ç Ç�—°ùd,"""=Aæ|ÜÚy»1ú�Îó]ZÔ}}QôkYÔ{®U*¢ED¤ØÒ¼_¼¶ÖC*³½;-Y“Y–+¯uˆDDDº‘ÌßÖÎÛ™‹ãN79w»{ïÿ¹î o¢îܽËRà[ÅNBz�—Š�€tÑšÊ gÜìCî“mæŠw§-_Ñ2 ‚6[‰ˆˆt?™óqkwl3çí¼g¼.¶hÉ®ÌÒY­ÝaTD÷*Ñø¡Ÿ;‘VΊ½;-ÆW™YØ5úu%]ˆ™•FW®s}× Ý""ÒÓdÎÇCÌ,žc«Ý£ï]j™G3‹E]·sL8¶: \6³UêÎ-""]ÁÂ._{™Ù.™�Y}”κ¢Hùµæh3ž=vÊÌâfvá ¤Í„kT‹ˆˆôÑґŽÎ×À¦™¹÷&<¯/)N†­dfÌl·è"=f6ýúB…6 ;Ñ""Ò¸ûZ3{Žpy©qf¶–p­É@Yôó¢6BK)0nfM„Ý¿ú^¤v`‘»w§.è"""ùZD¸ŒÕn„Åé*ÂnÜýÏ�Ïf/GÕ…ìTÍf¶�°&®ˆö½Ç–ëZoAE´ˆˆt–f“Sc®�îþ¼™­ö#œŒ«°Žp–ëÅîž½lE:е®•ceöoȱ¯1Ú—«›uC;q[z J¸®eæƒC#a÷µgÝ]]¹ED¤;[CX3nqîŽ.€?Œ$\gy0á¼&Ë�¥9Ö‚^MØUºµaP™sf®ý+€rÞ_Š2“ƒ›Ù{™_Û}6aœE„…¿è+ Ë‹™5¦ó<ÆÁÀ¾Y›Ö»ûƒ[—©ˆˆHÇê¦çí8´U8›Ù~„CË6Ñ�hé’ºÛ‰Â5¢‹�ƒˆˆH1tÓóö6嬉ÅDDDDDDDò¤"ZDDDDDD$O*¢EDDDDDDò¤"ZDDDDDD$O¹&œ½X¶ˆˆˆt¨ÒŽŽgf#:8¦ˆˆˆ„µÜ�«ˆÞ5ú‘®¯8¨ØIˆˆˆôºã,""""""’'Ñ"""""""yR-""""""’§ÿRX¥R1ƒ|IEND®B`‚numpy-groupies-0.11.3/diagrams/aggregate_dims.svg000066400000000000000000001220071501360642500220550ustar00rootroot00000000000000 image/svg+xml group_idx a result group_idx a, axis=1 result group_idx a result group_idx a result group_idx a result form 1 form 2 form 3 form 4 form 5 note ndim(a) can be >2 and ndim(result) = ndim(a) numpy-groupies-0.11.3/diagrams/diagram.docx000066400000000000000000001162711501360642500206630ustar00rootroot00000000000000PK!)Âeœ[Content_Types].xml ¢( ´TÉnÛ0½è?¼íŠ¢°œCêSuÑ^jdåÎxûûŽdGpÅrbä"€¾…�œ™ÜìœÍ6�Ð_Šq1x*ã—¥øµ¸Ë¿Š IùJÙà¡{@q3ýøa²ØGÀŒÑK±"Šß¤D½§°ýÿÿPK!‘·óN _rels/.rels ¢( Œ’ÛJA †ïßaÈ}7Û "ÒÙÞH¡w"ë„™ìw̤ھ½£ ºPÛ^æôçËOÖ›ƒ›Ô;§<¯aYÕ Ø›`Gßkxm·‹PYÈ[š‚g GΰinoÖ/<‘”¡<Œ1«¢â³†A$>"f3°£\…ȾTº�I S�‘ÌõŒ«º¾ÇôWš™¦ÚY igï@µÇX6_Ö]7~ fïØË‰ÈaoÙ.b*lIÆr�j)õ,l0Ï%�‘b¬ 6ài¢ÕõDÿ_‹Ž…, ¡ ‰Ïó|uœZ^tÙ¢yǯ;!Y,}{ûCƒ³/h>ÿÿPK!ø�4Gword/_rels/document.xml.rels ¢( ¬“ÁNÃ0 †ïH¼C•;M7`ChÝ.€´+Á5K�6¢IªØööD«ÖµÛè©GÿV~¶ãÅê×TÑ7xÔΦl',+]®m‘²÷ìåæ�EHÂæ¢rR¶d«åõÕâ*Aá–ºÆ(¸XLYIT?r޲#0v5Ø�QÎA!ô¯…üði’̸ïz°eÏ3Zç)óëü–EÙ®•O¼�–Þ¡SKg¸SJ˽ë¼ïÊ‘và‡¦òY)�„ÁOø(eg©8À2~™cþÇ…˜''·,]h•S˜Aöa#N†¦c24Ý!šx¨üdÌòr‹äÌgx»�8æ­Ê5�ÆlLå,ebSu–ÒJC#¹â6o@N¯óI;âÈݘ xFqP¼wþË?ÿÿPK!{×+Qbʆword/document.xmlì}é—Ú¸Ò÷÷çœ÷àtÎy¾�ÞÁÌtž6³ï;_æxƒ7¼aøëß’ :tO’I¦ûævî�Æ–å’T*•J?¹Jþ_b[™X÷ÓuîÈOÄ]FwTW3�åÃÝx$Ýî2A(;šl¹Žþp·×ƒ»ÿûüÿþçÏ]QsÕÈÖ�0$œ ¸óÔ‡»UzÅ\.PWº-ŸlSõÝÀ5ÂOªkç\Ã0U=·s}-G$�¯<ßUõ €òDÙ‰åàîHÎþššëé”e¸¾-‡Á'×_ælÙßDÞ=P÷äÐTLË ÷@›àNd܇»ÈwŠÇ ÝŸ+„^)¦:þœÞð¿jÅ�rÓ7ËGàs¾nA\'X™Þ—fü(5hâêT¥ø¥FĶuÊ·óHæ«òÎMþ–>(ûòºâ Á¯ÈÝ`†–¾d[)Pÿ~éÕ§Iâ¥Æ{‘8×á[ªp]æ©&¶l:g2?ÆšKæÂˆø'ò]õÝÈ;WÇ3ÿµº³9ÓBó;jFpxä]6-ø._ ÝáJöô»Œ­ëKÇõeÅ‚íH&ƒ$òî3( ÅÕöè×Ë슠l´ÁÃAHF䤻SRY7äÈ ¿~Ò»HÂDz>þ)iøWqÃе�J,[wH•XP™]18<Ü�"€ OV¡B$ºV]Ë…!.G¡{—ûügªt$ã§T}ÉuÂrÊ�jB'‰0¢] )¦]q%8Á“$XwÎQ±�W…,‹À¤QQ¨ôû+Ê%‚æ)TæNªHO/øs|rÁŒcí£SÙG6¤M¼*Š@ïýý »bøY^.}})‡:ªvˆ+Ñ„P±)øIyÃpoé§JŒ�$T}SC|Àù¦ðhmC- ÷ôÊ©'ðã |˜ŒpfÉ æ£Szu(~󨡸úI‡îŠ–n„?öæµ }_©¾¹\ý`±&•¦×~¬ÎéË“yõ¨b]²\±Z®»9Ñ"sØ0ý ¸ÐmxÁXHï¾<]+²‘ý€z =?%à,Ž[+�qì;Ç�¤wäqp\J’ÔùKø@ •JùT€®’9Šù}RÓ¾85?ô¡åg5ÉÑy¡\F¬½Ð ׉£K}�G�šþ=ŽKõ8î0+�P:ô´DN9¬4HÄcMµtÙGe]Ž+¸5L ”ëi°¢êª)í+�~]«AªÆž$^¨1šfD,Ì´eXÍ3£Úcm¹+®ÕSÝðKk|Ô^ÿXY+ߤ²¡~¨B?·PÇíù®kܬEÎÚ­Ã`S~¸Ó�ûj 5x�+³­`…ºï€v†(SüóŸ�(®\0³3}™¾ÓÓÌQTý£�—^QvÔ•ëg43Gx`¢«Òùª…FC0~уÁ—ÛÀ´=Kï¹@¦6o¬×t¤þî(–ä8‚*@Ÿ*úÊt40‘qFËU7zª,yïFaÝu$Q 2dËrw]X„X²‡�Õp¬aÞÉ L�'xä=Ð53DÉŒX!è/¢¶yÅs½2 .p�þ"ÞzEÏ Ld˜×ÎÕ•|×~¸ ­•¾yº†èáçû< à2~ñ˜xºMé\Q�<¡êɾ¼ôeoõašÌ³/ž`Âz‚º2£B;X¢€º@…ÆP—‡ë´EºaèjXI3!0õê½£'y‚=½±ƒu`M߇¥[ÏϘÀD09Ù†i¸…fÍ’†R†:–¢vâ*jˆ©J>äéùð² ª'—i ˜O,Ï¿_ `³ü‚TYåLäƒaõ„Ôw-]RqÖ¢²°àš05‚[€ˆT–\L ßF¿°Úx"-r¸ÿ<ës_Þõ`N¬ê`v¢‹‡;dt`îA¯ËE9n! ùOùP²å ¿�k™šêßøKE´ü Ö½’DÀ?Ä}xñ"Ü¡WA$OmÀWÈÒJÉtz†ÄÅãÕ®~&+«*T:Ý"J�½†4úùEâï_<æG¯¦ò÷=/ŸßÀ%ƒ¶:¿l›°b¸Uz˜œªl¤ùOHÛ�X€V Ⱦ›JੲUZ 4{0 ñ�½"åb€’y¸s�WwP‡[é(?H"<}²+Šm$û`€Zu´O2 oø†aóv—O”Ë'Nd‹nªæ vøå­Ó¥ªh V¯€J…G©båÂ,œÞˆ! kxÀ‡ª ¾N�–3D¨KÚëHÒFÉTö½£L† Ë÷4.¾’Ê4/êÇ`9d˜Xd¿ðõÈo<˜@��V, Ç{œë¨­Ñõ—i&wžŒð¼$�žWdu#³è™*LVq1@ GdŒ`eôW‚äÿ¯°��çSóª‰"9‚øˆÿÞe\ÅÀ)”EÖÖÐvÐ�ÉTO´z¸³ÓüÛ§>VúKm÷Ä#I¦yéc*¹Ý_�‡NFRŽ&*D2."¬)ƒ_gô-ظ0R3ð …ÇiRÙL%CÜzô�|6ý‘¹&À‰42<Ë24•çðòfI�Øü½ùè9çG0Ä2P‰G©–s*.ê1Ÿ!S…�U 2×T.[ùÈ_‘Â>²i½V°Þ°tÜm«Ìiþ‡AzP÷?B÷eö>X \ÄÇÇ#‹/3†®‡&Ä�ÀÈsÎGþã#™jç¸x. w!Äg‰ÆÒ|5£b¹EîQÆ’ ›H¹ðáRð¤ÞîN•.Ê ÌQ¨ÿXçÒtîÑLS¼ç¼ð”u-ÞSŸXHÙ™Z¸*2pµÂY‘"áúp†˜žÏÆÙ±ÁÒ"¾´€zàÞ#uw�Ë/¦:ïK*²Aéiñ<*ýôÂùªq#›íÅÛï¤Kîãk§6߃ö5 ôeë¢ùPÞ� ÷'´ˆä—~Î…T¹©>Käôø ‰øQºOuoÑ65�$$¡K#Ñ@÷,^ Ê¥R_(-…ºÿê}‘ZÌan 7ÝJ þŽPú”èÄŠ3°à?cR³v‹YGk9«Pi ýq¾Ü.•¤ÿý�ðÄZÝõicÌåûÂÒâæMbHˆn­ZÚŒ’fkQMØQu¯Ù.U%†ƒiÇ=äò'âwj²‘ÚÕÎ’ÛFým‹˜ °sÎôÙ8ä­2ÙØ•Êõ]·ÝhÖ¼šåùß4´ÚzŸ mßóCv±K¯uX²MMœYÍÖ:V§žÆ‡ãîv¶slÙ_:µ°1JºAߨ4ë£|Ù.Ç¢œìòƒ­SÈò‹À´y�šØ=½Ôa¢í<…Óü:6×K¾“Ûc‚�`¦«­-±ÍNËœ]ö—s¹][øº³&7¬åHÔY�>ê¬ü'Ì膥ÔÚqËiX˜ Rën6mi»;íU”-ÇóÞÀ˜Uå]b®GfvMTc¯”ç¦Ý–§Ä|O>Õfz¥¶ã+c%×ç+gäVm“ÜÒNêTÐ ¹qc©efyi­‚¾‘,i¨ÄÂu™Š"®Õ„1øê@S(¶Öæ«­%巶¼b.6É´knç˜ EÍÍ‘/S›y;"-2ì—i"§ôlz·›wˆ„,,†´><òØu.�JÔˆ�úùáŒ:• ½æ\ÞÐûß%ML°;HF²³ý‚LÊ…l)ŽrZNk+TcÎFfOYUs›ÐaÔmuœÓ}ÚSã�øŸ1A“ìhRÖú0P¤@>w³µÆ¡E�÷ 1ˆç{–Q¦û`"Y~õPéfŽèíL¾“¨ƒUi3þïÚ˜ 8¶šŽ= "ïvt¯Î‡>ÏHoÍ.‰±äoK´e³lœ�Z KÖ•P2ܦҖj‹ƒåì7®ÛÌ^¶g8¬¬ú˜àÁÙõÃU¿ê‹>3žYÙv.fÅKƇiµµX ëÃöšÜ”I®ŠUN^LØh2¤ ÕÑÊÊË’œTëɘœºöºŽ ê6µW¬±È%77�ËÒþBìmb*œÌ÷t5'SÑfJe(fm-¡ ¥6½U0V¬¸ÑZ’2Y»~kƒ jåÙdË.—dÛädV ªå²­¹åžð·ƒfw"Pn.ét6¼ÂLFÍÕÜ_ÓÁÎårí½óB¾¹mõÌy{n1A9”šœ*f݆Ø\O;|5Ò�R-Ü—ùUHä<_‘Ö5£à•g‹zgDt;“im'ø^;«æ¶Ðh; 5‡r¹9ÄcÝåønÕ_zžäFbÕ c»ðÌ8×\%†ÎpíÜ<[swÅÖ•M[ ç­%µ‹·�Zk1ær‡°ÒXw’*9LÒ^&»r[ˆíz–ß–G“å¶®7 ÍÃÉr²1=¥´"5—°4i4’ÚÍ¥Ýã�Ú©Mc›¯fÇ%†ÞT§»¹Õ�›¸†‚Ÿwzƒ·©M[ù}ÜgWd+¨+ûaµ+]®¦Ñ�ÅtÊÎUkK¥ß'+’\pJ½õAéK¥¨ÒvÈ´st¼Q ˜àah5ºÓÐÝõýÈÈî*Í ßîÁ(WÛÐ7�Qû°ÉÕírƒm’[uqÁ¬ß$4bd%m¯2¨IOvGÜjš/a‚½škmqh”4µíƒéræp\cÒë°Ë°2í®¤)8�8 Âcz­]Cë°ÝÒ"Þt³öxL´5m¬{ûCÔva�*”Y-žÚe"; É½¬›R;ëNؘ‰v34s¹BY�J-a× Â\€-bEYíZ�'Ð]*ØB†ún«ƒ½ºç7 ­Eª=¡ðŒVÙ÷†Df4±’'Ö… ¢æ„š1%ÑÜUs[ÑÇRÐ_ ½Wò¸†k»é”�¬Îö‡¥M» ¿àu¼͸I9/4<»]‹Íþ–ª3ry�‹©ì®4¨ì«>L‹ãv›„æ˜$ui?¥L�j½„ï×ËÛ꼺#áCD 9‘yQéöÝ\%<¸mÚè+B‘w“C—#vÜõºzØÔª³.‰•«ÓÈÅ9’–ù¹Nçטeé,‘'Ç[n6ÌÆ Øøv+›Y©iP “L5¢Ùp<©"ïÇ£e‡šJ³A½'“sj!ÆÓ £¤sJÜo,¤¸‘[õÚ…â, &!€r¬ š•„•Ð’ËÝF+‹n˜~EJmtyõ×§Ha& b1e‰9`Õêä0"¥šô‡»ŠØê W=Ûí«?‡WÅ1êTu*üûB0½�ÑÃËiQ©un´Œ Ù§Ç 9%Ì"¡Œñ5ÁµîkÕ»–—òw½§5„z–{hO‰b�b÷‹Ù<Ò«d¿ú ì ÔÌÎÍAðïá»K‘4C£Ï|w£«éþ ¯ã=+XΦKU´–ý²€Ew_c¯˜E‹ÛôOŠŽÿ$€úwÅŠYžâ+VÌ’l©Tæ¸ÆŠó–fyõ?+.�à'tøg‚ŰçÅ'ZÌSyA¿.fÅ>ƒ¿/ÁÅ� b¼¬Πà-˜–Á)L<‚¥Q¦ä&)6oü> q&L aMû¸ø6ï5;aÍ'øˆÍ!ô„ñ+PAÊ)$‡b¨l3\r4tçÕ“#*|Bc�ˆóßcÄßõþÇ`ÄÚ&E�Í~#%AZýž·É@­7 üÄõ>äqÛòIâËÛ–ÇÌÛ<î‹^} 8,|„‚>­BÂu±§™BRiêqOA§=Ï‹=�㾚“Ž“Ë© é>ëE¾�0ås¸s3hC&c:^À}ò™J¹äÞ†8rõ÷Æà¯üÏ@àñ\‡Àò�n=ÝåLaC<ñÁæ©§†S~Æ“ÖešÄàþDM”_nŸnr~E4ݾ¤zL¹& ;œhc5åêOÚ- ØÄü›íœç¸=ðÑJÁb?E•­ô'Ñ�E€åb#0³vMçÄÚ&@Æ)ªŒÑlØöÐLÀ‘ñÖÅmT+wçÏcÃÓè·!øÏBÃ&þHqKË\:ø#¨@|DŒ,|Ä�1d\¸€Œóô̘Îã—¨16Ó¾5FRvïé°eå„G|7¥Ÿ$bÐùŒ!ãºÞ†ŠxãæßÌœñ q§Ý‡Aî#/Ÿ>º¯�eïÈõ;rýŽ\ÿr-,·îj­ï¹®V^F®ÛRDÁ.AÜÏ͹æ|‚Û‡UînfSø¬(Væ&=-¯ Ø£ïÏ7Ĥ3¶Î öp¨±}P<µñËqvlÖܨÛÊûÕ NïÝáB)ĵ�YîX�É”4†>2FŠ\kI�ÉÚ|¸2÷Ûh HÆZÊu)¥À+²>˜.DߪÎ[óv5Ôö5‰àk�RË®û«ÛJãE'´Gƒ^2�`,DÚSf‡“çëÊÜŒ£¬êqqY“·|¾Îì,¹ñ«SŽ*›Á~\k9R®7c#â`Ì&Zlx=’Æáê0ªô¨Qºáav|“p(·™T¤i.æÖd©Ó‚"–êc«Û'ýÉ8ê/Æí„ã·ÝE¶±84ù¨2½9±2 ³‰“_Ð5k:É.5\Ã9'äNµÌ™­ü°D( z!KÔ\ G±0_jd™ç„êx…Ýìp–ï(¡và>ÜÛóU¯¨kWß +ÛãtLpTg|azrí•aµÆ�8ÓÍiOªIÔß ¤]“[rÃ|K¢gI4¢ªj,o¼laPX­ž°ëØÖ¸æöW#q›"†£CÒR§áx·Zç'ÓMctzæz/raƒ W•yͬZÁº:s¢¶èLy¨ªª*ä¹±Ìæ¹.­’†çØí�fq g=ª6ã8bäz§JôØiÏ”–b¯ÝÍÖ ³ÑÞ£ÙÞ’­M;J¸¯WºÙJB5—ËÉšÒëQ¯³Må…ÀLÙZÌŽ4~gwô¹> ¹9éõ’¹È˜6[±ô”}P÷5Ψy%AËÉÒFŸF{. �ï6ó­æ¼Ð©ƒ.åa‚º:T‚ŠIïÌa�tk}ˆ“v¬øëƒ&mƒ±¹Ì‹­QÐWô,¿=ŠÛPJè΂1½$ÊÕQ­1£Lzc·`/‹Íc‚J¯I„ÒªF8c�Ú v‡¬'ôzsæ�kí€15&Õ±Zˆ™ÜËö¡¶OGÊ:Ä0vó#ÞF°“Þh ¶°ç빊Q7; Å7&Ukhq£ÁÄö„~­¼ p }NM'Ñ Ûœ5 'º´WÁû%¸¯{`ìV•¨…Ã@ríº°#g±1n®Íl>™/Ì’8èH^oTiQR*T¡yhMsDȪe¿2iUê«Án¢­+íh1]rÌArS±n@)]w_Xoéq�d´d/�]Ö ›»tŸ±›î×ð «|iX˜´š†�²=ͼ‘pÔ’5&KŽc ö ç�Ay0ʉ½r­‘]÷ öBv vm>˜€>Tz£M{é.T[êÙ}Ž\&ÝéÊH ר�Nƒ}RÑÛܬ8ç ÜKáÑa�‰£kCxr›@®&çÎø~¯‰Š ‘©G~±:BvÁ•• ûNÀáSêÈtà›6šF½Œ`·ŸàbqÝ3ëbqŽüÇöœh= d^ø€ü,!®H¼é+�AµúåQ.UX {K�ûþ$שï�¾}>é—Ÿ©J^’ŽÞ‚Á¡Žx—ˆ�f/æ›_6¹¼5‰*éz~8éö}Ö@ÐÉy‰ ßuDê—Š”òqCò‹«æ£ñ ³Æ»eù¦g�W°,w.•KWóÃiÖ€mÒ÷µÆ+Ïì¿?k¼Kě֯ ïk�7-¯`GHå _F¾`Q§YGP:£ïxÄkàÌó³*| Zž—C€ÉÍÒ1ñ?4f xß‹%lÊ h8 hþ^1c^‚(äç…dä]¥޾ÛIüÕ‘Þ^¢ ?Qÿ¾P¼[oÚº ?½[œLïÈÚ{™xÇ·_ úÚ“Ç}á}ò¸Üw|WXQ¼Ââôä~ÓIza}ú«–ï¨æ› ž�Ùã](Þ´PÜç?½ïš¿¯=ð×uÇûÂÏħW0)Þñî7­(XúSþyàêy£Ÿôü�+_þë^nû_�¼_ýu)T/Ð/ÆèßÏKH�Ý€Åíûy (ÔìÅüôóÞÐÁéý‡Ç\ŒÛ_³�ñëO5ÈÓ„ñ^o~*÷{íPý®‘ª8Цa+üçEª"J"É3’*Ú8�j@Šù2U¦~z¤ª<Á^Ž'UéürXÂÓˆÏk@³,‡,¸g�KøêTƒËXITžGÇ  8Þ1¬|åæ±iœ*)Íü\¨*øbúI¨*ìNäßCUá“ n³ÿ=TÕùD‚o�õǼáPU/~¶ýíѨÐìv´¨ÿæ%°Ü?Ã0pê+ÿ=Þ|ÄóvO|¸ õ»Ç›Â±üÏñš`vA'P<‰äá&ŸDòÿçñšX‡aºˆØD}*@”¦4ÆÿeÀ&Š‚äËpMØVy×ôSNx×ô~ÐÀûAoÿ �ÒDØ(4P]¾®©Ã÷ \Sßð²D®PÛg‰•"nw“¦î £F»�Ck¸íZ%ÈÆóNà ÙÄßd¹˜Íî6Þ´‘Ÿk^Kõóù\ÏÊ:d– }1ýJu’k%Ä|y`‡ƒq8 ¢ì,ÁX‡¦?§+£Ø*VÜUÌh­:V›aÈÆ¨À÷VŽ%ÒAk3ˆf�èÒýMv$D®ŠkÑš¢uÅ�3yÆÍ�zfS6)÷[Šh»z]žìíJ¯×!ÖO…ìÆ]Í\Ó9’[S³è±œ µUÎo†µy?%˜Dô–Í–÷y¿<.0=fØá6 ´‚˜õÍÈÕ¬Q$t(f fcyï7×óÓÚµ:%~:X¬ò1×lwƽæÊ:¬\Ã~nÍwhZóÑÐRºUvKo šV-µ§‹¬.»îÔ€ˆaýŽ\fÛ£ck‚Y³Âé*™ÒAcRš$þ@‹Û‰¹Û¶˜ D�z¡´�6³ýp¨z-­µv;Œ©S·36ÉYsÑ ‡³Hìè9a?†¨µÞb‘W|¥P#�úzF´úûQS_Ę å÷Êžt­UP™é†>‚àVEÓq sV£³9=lf»åÄ!»‘8k¡ ìç¨AONÑ)§§áT5“Á!r–(2N]Þ ð>ªóá¨#HõQ¼Œù•>oÒš×Ùö½ÐÛ¥ØlÖp I‹+ÄÓ�±\E�@hTÖŒµ_²X­ "N¡ÍZú¬º"œéX*Œvb’(ô¬ºfz&eïÉR·ž Ek‹~>= a'ƒÐ«›N[iêyW5ÄŽ(*A[RFz™µÇ¾�o¸�XYŽVLãÈ|¥Ú]kýõlÓe9}lšVÖÝÝFjh1ŽÉ$ ¤”÷³ˆ4ÔÉ.:äºù–Ç#-^‚6Рm|‡:Œë#¶— íØØˆµúº4ჃlNò¥V=¿VÛó4äÕPª÷ë†Ø/INH°êõPIêç3{¹œ°+Ý åºmf#î—W(\Vú ó½÷ôß³ç¡ã$úõP¸:#é˱;)Á‹³w®‹èõ’GKê¾€Ž˜TòÁ/?%x¡ö¯ 2(®ÓFuÚñbÔˆU~�Ϭ¸Y˲„Ä|*ƒWD;*:•!%ZåiN ÒÌÓ™ ãjš·/”„ò]c‚,:‡átsyÒÜÙôÆá�Ÿw)ݤC;¿0ÌÇOÞj¸ÞìÄÎ0/m5¼{å¿w² çìBüÀ…}G´ý¸àЧë$tã9 BÛ‹Ïù!vÞŒE±g÷«7Ù˜ø ŸýWŽä ˆ÷—…Cýù nKÿ•ýyÓ?ôßêäWˆŽñë;™+eVº½Óÿß8ý¾Â§á¿¾“ßm¬ëo8_ð(Döô?ñO¿0$NvùÏõaþÉÒR ‘“�auvOIZ~¯�ž3Ñ”>¼Úê½ðn»Wð3gåEÃÕ(²¯ªô¯, ^Ó9š�#ÇYÎã,šÇ$Ä÷·âßö3eà%y$_åÃóòÛrz}I5ý7Z+¯ãô*QèhÎ8ÇÙy¸Sduƒo;ÚuDàáJÖt[NýÄ Ñ74ÔŒK3\û£Üž©^uáô3¯é„û.ž`›œ¦(î÷ï.oÍcê}™�dà,/{LÁÄòKâ?½1Û7h;ê<Á_©‰'Ù³þ „þѽ¨Ë«ïþô†8óÂÞÅ¡%“èZ©½!WÖ]ñ߬¨*·gŸèµ« ؆À½4¶ë…ÎÆþÃl‡wRï…ó( VÚit©–.ûh<^ŽA¸EÎ]Í7«ú†ª^Î[×Ùñ¼E䉼ðÌ~Úïžÿ¦ž³y’bI˜šžç,Çò%’/€+Εç,!PžóhïÕƒsìmÏÒ{n�I°+ÕýM=G=70CÓužz.�„G¶“¾yº†èáçû›þ�È\“‚…¥42<Ë24•ç hØÞ,éëé›�>à‘s~C,•xä®há¢ó2ƒ·–ιOU 2×T.[ùÈ_‘ Vu¤Í„ÑLpmìRÕ[MÇ_U‘w;ºWçCŸç¤·f—ÄXò·%Ú²Y6ÎG-†%ëJ(nSiKµÅÁrö×mæ/Û3œDVSǹƒ³ë‡«~Õ}f<³²í\Ì0Š—ŒÓjk±Ö‡í5¹)(“ÜÿgïJ›E¶ö_阯Æ\Ù—¾17‚EQTD¿¼²*;ˆà¯Ð겺º¦—ꮚ*¢ª1MÈåœ|NžçasN ô�ŠŸÔ%J +×_ê«—¸\Û(8´¹‚V€TûnïØ!îHÅDMUnq,�¦[-„C®n¸ccÉõ³Dmc´Íc7[~!N�KÉãu”N�Í-›< ‹˜è̉²'Áª6ÈžöK©@š®2™« õËÙìH˜ºš¸Ûô€ªLÐï÷¥ª(hÎ÷Õaxáe 7æ/ ¥özpŠé w°g+ç€àCMß‹—q`Ñì¶Dé‰SÂËÕ–æWæÚ•Gd(¦!ËÕ¤wš:Ç *Û¦dÖÎ^ º2™:.Nùh’Û) GÔ :õR¸¸¤.±d[Nø ¿Éec«ÁóÃpœ˜6ÞsJ@tw{ij¨‡³±Ê;:¼‹iUŒYÀãË w�'„²žEgâDm—ó$޹`(_½ ™ ‡ä­ÀíLȨx~+U¡ê72fÿ2rpºš¦CºT# ÃüÍ`†Çü�Ñ6?'ƒ(aá)¶ŸMÆ40wÜfÁ_äè AìÖšcÁyÚ[Ìi².š÷P¼¼¸±ž�§äèrb½Ãâr1Ç»Ägb88 .^”ç•CXKZä)6¢™>'Ä3ì½1OlÃÈ»–SÓi ¥Èr9tÅÂÉNLß�Œ�£…!ª‹îäƒÍÜ6tŠÅ)/¡[LÏ¢9Ãçì®8Z½`½†.æ Ùˆ‡Eu‘šÍóò×’ë¸Ylê)(\é–7”z‘ŠØé¬ÕC±ß§x¦N�<Ë [qË€ÜI†áÌÊ—Fcª�ÚŽ]§‡òu:¯ TûŠ>¨Y'k"Mnå Z,¡>°hÜ€„‹œ”ÓDE4Œå@—q'ÉÀZ3yw±­ÅxI75<“�·{./CÄÜÌw)ÏâŠE%O2bHÔš›È 2Æt^éHïÌ*ƒJH�Y\KªäÞ†A¶ëÁš!f´(iyÌ'ÂV8Ãù2„N;½ÔiΘËQ�_" õË… ý¬ž1”?áëy<·rI±wv ¨\õû#hsŠš Õé­…’Fdk=´‘ð:!´e¯ØV Ž;±+7&4Ãx8ЫõÊ™!Ú…Õ”ñB‡·ÈŽ+6GÌhûa!‹»a!ö{ŸúËg }Üÿn]ïi A=ùEín=—.¼´!ÛÖ<@™¡êÛœk~þú«)ì�v‰b\IìÑÑÚG~½üN­&ÂUGüš¥j½–ý´€­�,%B=·@æ\ÖÍÚ7­·íŸ6~öÃYSF�]}üm±b£¨×ÄŠQ àR‡†ïXiˆEˆÁ«cÅŒü¡Fæ>‚UÇA¾¶ sƒNß–$_Ͳø'ˆ�Ñ߈?&úÊýþ¶àÛK<‹ã7˜ø�«Yhw$‹7¸øùgÿe¼¸F/Ðó PqÕ1û5¼û— f}øðξ¡±WÄùï1⯀zD¹Ajß9FüJ$‹÷qÈëvÅ8LNm„õo–×l³�ùã»}³h ö7X´Æ§Î_‰báV·6$|­p]þuŸHcÌÀEŸ;ȇ5ºZ?°j× òÿåçèC €Ð =cV¿÷`Êjë×\�wüŒï�ÿ÷ò3Ãô ?#µO�Õçg$jXõ1äJ=‚\Ó3¢dóÆ#‚F®ÝœoD]=3wÿŒ­´Æ¬¯øh ê>9y�ÁþþðlGÐØ!¿òûO@~ûGS°ÏßCÐx0ò“¬ó%pÛ–\PÏÅm•újeàt¨‰9l_h_\f6rN—>º =í‚¢ÛÞ…¢·zî¯]øÄNLßëúÞ�ÄȪÒúý�oaPL™…ƒí&u!\¦øê¸£ 4¥H½_îWfûö¨HØNSôGã˼*ÝTc—`UŒ�ÀѸ”ì³:TØK˜ÍWfŒ+À,§ñÛÕ„Å™DÉEË<ê½jAÀ*@÷ò-6 –G¼Ü¸ÐQß ‡Øñ"…€�€Sd»uûž4™4"¹p°`KcÍpÑB%õ ³£…(:;—†ò,�¢W�Tjš¢ã“ð7 O½>*¥¹CSU«�Õ²Î7B¬0½D±·»Ía7%y¹wq|ˆ<€£1ˆ«H�cµbÂûËíFÃÐ$�Xº,÷éªÚû˜ÂîÄøŠM ©ÍrsZÒÖ´Èdj›q.ÊÔ‘ó+¬š�š:ࢥÃTK·¸dæéÚårØø‹ 6&C PÛ¡‡A¦ûÊÉ5qiÙH8ÓÅàDHK÷p(<¦ÌWرš Ñ4:;h8Ûöäõ!”’�ÕwX´Z ¾™,l,L¦;‹\Œuíl�zY™M�®x¡’KGÐb|ʤÕQ6q±ê.#·ütmÊšzÈ"3bO1WÕÕzƒÝQ¡Lx<âÓåe.å¶!³c—&š=C‚Ý!݇ª=•/áÝ|“Gè1²l¬Åj—b˜Þ e*Ô·ìekǤ p.ÊFÖQig1CÕ_ÍŠaU�Û�ÆBŒY˜ecvZ¢®$Q´Òâ8;ˆö±^Nwû�Â-bV%H«ê­xy„Gã©KãâDOã±Þs6ƒª©ai—ª…[s¶`÷ÑÜ!èŠô,kïð¾p²÷P�&K»ð(±T¦ç¼¢d€Œ=pJƒÇ@dS #•T‚‘f�›qÓ ü>K†'­ xq¾0R±\”£j|Ø›ÅRUâul!u—ÈM�öŒ˜§Q0BŽ+ϯ ƒK©=}Þ‡ :_�^_e=Ñ+s–\§¬.zŒ4SIæ"¬8QaëÈÇ•¢¯ £zù98O6tÁe\ÐS0dˆr¸,¹ñz?Ô¶%? Ôh~’ÎÓìx#*\\©9 8/7f¹ –†è «íà’CM 碗HbÖy] —䌾hÉ>ï �²òhþ.ru 8·–¡uÿ|8—•A 1ØÄ}ÑEªÁnc%К½ÕŒØñì ?Îó­¯üH£4bDÕxéÿ‡âóqå¶Q@Üÿ°†ŒÐ'SPOæ�^[ëïaèd p ÐÏ!fþ@>ØM]FS ÏÔ-›Ý— ަgp~HW»My™VxóÿÙ`š„výäm ÔúÖUñq©S§6 MÁ—z` Ò—kyíz·ü©ÿ=.’aY¾z™¡ V®ÁnÐc¿%³¨‘tþÐX?|ðsƒ‚?mýyE‚ÆZøhᵸ¿”í²Ìõ4o!˜Ÿ€Ò¿D?Q§öÇÿw¯æÀyÄ}sÅg^)|ðRÅÀÎ'µÀ"{�Ö•kWÑYlùþ]¢P�]3Lk¸é– �ï¯Ù@uÇÃÇ¿=Íàõ ž†÷lšƒð½”˜ðo¤Î¸gMøþÄnñªd*]�xÂúCÄ `x RήG¼k]×R�?�k¢é+™ŽW%Üú-çˆGñ¡v/^r,î~��`øðž> ä,îkó|vå…`ó8Ú(6¾Ì5fwóÇ@´çùóºËð7~ç4¾Þî7p»nÑu‹fK_Ãa&øæùë�Wå7ëü„Oúà ½žÀ-ºÆÿ»eïòÞ`…Ð5þ¿¸ñ;oૼ�_5üßÂä4O7ètÍ’Po³½­ö³JÞn�C�Ü�“®gûWª ÿ·«|…ŸÕ1!}gKµ‰&_tjøæšÑòšsÃË_Zwý.8õKP¤ÎF¼+ñ²A]Xὸˆ/؈ŸAxMɆ¯°Xw¾–PϺ×øÅ䑟f±Y€J~ÇDøšfáKV¨6Ô¯7]Lé½L÷ ׿Äèÿ½4þ`E�+ø®\Á7)w=à«zÀ›ºoà$v ò{± oàv�ÿ^¿ùݶä{>ƒ7Ðî\„¯r~@ðkÄWKûqÈèÎ ¿ªñ_X!´ V¦®ÿ=#®ôä©?bi ÜËè~ŽÉ½V¾Î—�¯GèÛk>Ê6þò›²‹�¡¯ªD�qÊàp½—ø1» Ê ¥¡Óë+Qü v1Åqªö�¾H[ö™Åc‚+„¤k�‰šÉ(¾j¼D.ÖPWµI† ¯V†â�_¬á*ïøÅžãûôø;~±‰¯`5û ŠwÌ/öbèoˆ¿ž·&ó¡:ø�rÀö…LGÖ‘„µ\üC j„8ôÔñZ}%ûïóQ#Jð¿Æ¢Ý�©-8QË}(–lÏ�JŠ4 �lÉs$a`Ø7‘„Ýë/@÷l`0`ÃÁ©V�á1‚€Ó�ÈÀZ¯¤#k$nĨÞľ øp{û‰VCGÖ‘�ud`ïŸ Œ©ø°×ÿ*2°¼¨e ÕNl’¥¦øiÀpªçÀcY]ªë†¸e=yäÈͳ$Õ0:ži¢‚$gè Ȧ ‰Ø2³ýåÒ›ïf%¥ù|@¾¥¼g»Q)Õ1IõElS½ÞbÖèb½½»t3Q.wª=1¹¢¬úi–¦1ƒÇÊfÇ¥¾°»l¥Ù‰8&´gNç7šø‚+Ì+î8�*ÃöÆÞÁ®°¤)pJg4v=ÉMÌ¡_T¾-Uù k¡),²~¯–‚@ƒ£© Ë;9žô(¢ÃBšJZ�œ8A¶{À&´d`J.o=ÜÔ¶Y„ÌRÍ&ÝD?ããã„Ë'„_ŽNÐEêj»˜ô†ë‹£Ù©Nìs?ïOS<'.'/Å›N6æfÆ”‰=HÎçšCdËØ+‡g9<9TCËq¢"±R{¾Yp‚Î�ppŒ.•bå $FK†ØÇµ”žµ)n™|_¸DÑÖ�,ÙŠ÷'ÉËàÜcaÈ™,Ñé ]—§U%ìOˆ·èc;Q.H>Á…Kòó¸Ø�®­Ìš˜šÎ“±\Òq±sÓQX.qÖÜ¨Ôæ|€ø’[êau@Iv•¸³±ˆUÔÚê—SuSá¨%Ÿ‹#†’çÍ-W–�i˜jö—ƒ]È Ùþ1ܯ¶Ó¼Ø3{“×væšTÙÓŠF€p�YèîÊW#›^.ƒj@ÔaOÚSÙ=z­„A9 &ýI¯O(’eÈYºƒ iC…ü…Ôε$D©OÜ e¯t%•mѳÂ|¦ÆÖ×GÃ4ÎRÙׄÃç»Ã¨e¹‹J@ê>~âm<4Ý�oxän:’ @‚¦Œ“lï8‡‰¼¢Ìmo.úû¸šø>Ø¥b j«´0[(šàíVÍ-_†¼ÃÌÙ¬‚r8B¡–¡”Y•H1_ì÷¤‰=+ç¨ù¥ áÌãâf{z‘ ŒM±” íš8‰«¥ ^ ;@Ãébd×êq}d°;«'Ù:p)”N¹ÒæÑ3^÷HS4ãl�‘RyB�C”6bÉÙ£K±2 ]òsÓIhñ 'Á–;ãÃjM.5Ý©–Sø1fMÇzsËEP1¦æÓê†=õÇ*B§@#pÈ®r†M^˜ŸvK—Ð/|¶XD3û ŸìãR×—:•ÍÎ3éˆ,}­&3¯d`³$É(tÔ?ÉÐbh*}Q“ý©çöÒ7œÝâÜ/h8µÑ¬2”’W.Å`áæ}|ÁÂÔ_‘üè°4-5¼¥|›™gz²RÌ€ l'Mv¬?¶éþzQ³eñýþYbž¥ØzF¢¹åº1>ûùNYˆk�÷Ú� ÿjYÉÁûJq×Ùè±6Ä£¿IB(¼¬)�3k:F¼iC<*¼äwß" ÑÖp¾~ª ñ�!{çXÀk±zÕ°E�~_ñ{ðïÊÒ‰Ô_òߦoï…@:Ü�½; Aü÷|Xýž·!|Z)ÐhltEÇ[YÆ…Æ:S;Ír¥–mmØ;€g{ôéM®‘p~ôþíDsI�X Ù 2µ†ŠÚµB•Í·>ôŸ›òºóX�¾©÷A+Ôâ€Î­Õ cîö¨ò¼Y‹Ï+5[Ê}”L¹Û~½®Mͺ^Ø4û•+ìÚ 7 Ÿ-RÿU}¼'ÿþòF¤þÑ=|¶'¿©¿v•&$ú¬P8ÙêP¬,H, ù tKêðÙ'õ`ûi%8?ÔÚ£«fׯ؇WS0îa ‘¨æÐ o‡bð͵7…Ð~ê²8L ( _O¤a0˜PO…‡¥Q”Ã~Jhð¥Þï$R´ž?©'¯áÇN¤þ�û tj‚ ¯ê.ŸîoâóêýQ‹Ct"õ é›óÓ�Ësã«�ÛMH „;‘z0§v"õ�EêŸJzƒ9éá™7‘ô®MÙ7Æ“ît»͘NÒ›�¢TÒLÌ÷#¾Á;æþ*�RWCüáÀóƒýQÌY~àð’Ò1Ú-Ëâ†G2–U‘ˆ:ôF†* 6½88€È52=û E£M�Ëõ<ÑÎáçS'å⪜g²=˜ŒW$ðË5§—gRIBФÈ](m!j°°ØvJ¶Å*ß�@óáàг~+d fšµŸ&PÒÛðDPÃ=ÐØýuë/G}0Ï3¢\#?Sð{§]ÚÔÐÍ�Gi˜Ôî©ÇÛ…bk‚~.½ÃÊë ¡ˆG¿õIß@Û ±SD¬¦ÆÚèË4æ÷í¾+yp¢4ŠêÉæ9±³·óxǶ§”uúÃ¥Q¨Paƒs/B‰Ù´ ˜@¥u$e[7ñ¹³€ûé4¡ ow,7s/Ù6"Èû�åpÇN ¾JÒ{�7’Þ…k#luºèøl�ì¢Ç'V‘š[ÞÆ“u(|.é=º ãhºZ_%½Q¬Ÿkö„¨='͘µZ›ýdyrÓy`³ž=Ä{§¦ÀùbÆ"Ãñ6›¤ŽÒÓð‹u9¸½(‰¯·yÀ�ìJÚðt®I:6ÜØ\5´ó®Ç­E} ¤ƒµ3<˜;O· Åã_ó»ÃbŒ"«2wU)VÉÜ„æ»þ‚º€h:Auc3â6ÚH <™øP4¤åå@ ¨1“ðgG5wK€ñlÉÁð0²©D8Ƴ4Ÿ©›Ñ™SèBòн—P" ¡B‘›±Wa-\›½‚Uå=ä­5–¿Ä‹B ¥ÖOe,Ì÷´`f6�l¿\—ž,Ìz¹á ç"™ SeMô/ù@<ÌJ^–-ä Ïu91PrÐÈ„_©N2¶DÛŒ¹\uÔ£¬ ›ä›ÃÕj(Mœ`Aï²ýl¤îÖ-ôÖ,†…ÍyëÏ‹6Ƥd¸P.Äq´™’U!ã.<O¿Z sš#•õê´9G–*£1á1¹¥yŽRæç` ²wÜPs“D«¹Õt<^C {0o,Ð-7ÜØAFvÿp&e»ÃÅ£‚­ÀWj: |Ó·m&Uenªµ"Ø×52tePÿU�ü¤]#w�ü5ñ°Þtý«í Y|`â~>jøƒ’SÿÊAÛµç³ÈíäËI �;uŸyß¹S?‘/·ÞúÔ. v]t>óWï<é,m»Ã ¦ïͨ×@�{ükí¯²´]{víÙyŸÔhjÚ��¿˜TãK_×aNWæg‰ þé2 ]ãÿˆ ò«�nè¿›µ'y‡JÕ(}³sä†94ùuËs~#�øÁ VçÜHÉZ©à3œ‚V*á#¸fnÛ™•ÿïOÂ(¼‘KxtXÁF¶Ç˽(üNš/ ÿC¼Xî/Öÿ�Û pªî›ñÇ߇áëC^²è % ¼û˜5ÎÕ'¾®È¶Ÿô@JUæöàCÏ 0u @ÿ  {órµ›(Šq8U¯/ý[]ò7aýõÂNPé�ÕAËNù×ùí%—§Ív”î-†i^ï£ Öói¸Œ÷õ¸¯‰åj.¾U¹ÑÓøJË—ƒ!>‹–®[Íz1ÍòzaõøÚú“aÄ€™í5oÖ󇙀ۼ^ð�ƒsVÃj:pAu <°çõ\|\Ï¥Ÿ4vž Ï´z6ž™»�¥zÚõW=*å¾Põ‰Uû¬Ð·ªÍËçlm(hÄoRjˆÒ ÿÖíõ§î{Nø±>óß;a ÿO­t=—GñGä?ÔW µÞÊ7¹Õ­ôgl�æ¡ÿÖz8n£²ôäd-¼÷gMŸ z­õgs#t]Mpýý[u�ÛrîÏ7‹Ç�Ϧ]”^?vsqþt£Ô»Da®û_ÐôùtÁ3ª>ÅÜ”¾PÈíígŠh´˜ž<‡çNóoÙǶí?˧oÝóÉ�GVw/Ç.M0æþúP÷?á'ãçâ-û5?Ù|Pó“­À/³�f…*>øµÕ‘Þi3sº¹!.yMò˶LB4tØŸíñF\¤Ì8>±�¡. ìqUN¦;¡ÄW‚[ð9"@Ke3‹.ýN¨#“{ÿdr¼×OޏíŒFˆjý…y=2v#ñ2EÖ•)ŶÂ1cSeêçÂ@Gf�Ødí$ÇhÜpn=2ó™�"v! -p‹_„CøEa ¸)Ð…î„�r äÕÂ@–•úqœJ±Vñ< ¦›>ɶ¨~Ž¿Š•H>ì³”K¤#:>M£»ò–y¥a ðLR’ù*òT{¾¦—®Ìçꦤz¥­WÚ!4w,Кr|YJiú®�V”–!û�$¹Wô©q1ÑQ¾Q{³)Ðç¥T(VÑZÛ(¶®Ïå­Fä)1£üÌHÛ¹°hÏaýeZ¬Š£ß%ÅQˆ ÚëE�QN'Ƴœº‚ßò8Z}ÈÏûà ÑB¹_y‰0'SD¡â¬´¶AÉ„Ca !ª•'Y²B?Æ4µ`IŸC³iÈžŒŒ‹W[�kEm§ˆ¿ÀÓ½¯Ïe¦¾DSKs(&1ñœ�;‡x(#x¾¼h³ZzóqŸ÷G+…¥V!eŸv†XÚd7·<µƒ}_?�“²GB¹…BA êˆ{¤¸3ñŒë¡Ð)É�QÜqâ…6ÛÅÕšPÃ[9Ü#N’¹È#Ù;öZrÍÇÂ@åx£fåv¥ö§ç}…PXVR¹ÎCˆæ«å\çéÅ:çýœ«ÓÕÖÔf"!EÛ¥‰SnG55œñ¹b!‰•±ç ¡n¶®¨b§žRŠ0)ØíœÖ¼dÙÍ'¢%Hv8¨Nk\Zâ Aн‘y–²±œ÷—ÆÁhm É®ÿfEè› <$«ô2ƒ×8I$r*!1¤Ï}Qz‹s8tÚÎ_&–kjX*Y‰�êÑËŒDY¥ÚWôÑ@ÍÓ>P‘ƺ ª…¼.x™]HlÚßj—£úJ?NŒ�?›DŒç5ºôÎ*¦ðQÃg ’‘QŠîiB¶[K7l~¾Óü22EØgíŇû —Q�Çò8Ëå•·H§ºb’^S`% ÁiŠŠ ­�*<£Ñù ;ɳuz;‚Ÿ*�3l€YÜž …=Dèz ãüÉ ýq/uƒ?d3¾­¡,¨Szo»+S>™kèp?Íú—SEX„ØbŠ…ËµWfsƒÞª.ø)Š3n5>«¶!!‘o�—ÕCy¡©ár—nt>2Ên¢¹ûxÉMµ„�ÀÓÿÄ~Ê2Ä€ÉR`˜Ú¥`\æ% „D<×°a ä5„�êî×<ÃÏú`ý„~øí»ú÷A¯« ÔÀ‚ô'å²¼m¼¡^ßYi»�áW…‘,uƒ¦:®‘–käÙ1ze´»=«—a¼—‚JÿÆF~‡„¿e#×ãÏž8êý/lÕéff0E¸ äþë;÷B}Úbó¶3ólçíÌï¯&ÕÄî¶[Õ]¯4ý¿*]Jçýý€÷÷ßÐ<}7Ë6¶}mbzƒ{ÐÍý Šþ³àåzÝ÷Âvˆ+>Xÿ{†éâÉ�{Ù¸^/® Ú=½ÃO»·˜Vÿ¯¿öz àß5‚høË¼ò-à4| +Ýð­:½Ýò®Û€·ÎMNøt¶�PÝ”ÖÑ2üõÇ·ÑA´œê퉡Û>øø‘þ4ŠŽ·² Œiè3läº)h¶Æ­ôõëѧ7¹&ñùÑû·Í%a4bõм¶z©íQ“?ÛÖá¡÷Üè î(¸M¼þ‡Ñ2´·v»£ûp?ÀP¨y8MÎèsžu@H”Nz3Øž'÷®Ÿh±ï¥Gº ¿º¶Ï0|EM#À÷—ÿíòï Õ#^hÿ+Žb$X=¾ÊÑ uÒt�‡j™^>ó #$‚RXÝÝ_•€�@ èE¢„ï$`€i¨ÞzÑ9\Ùn‡ ¯Ã¯æ_óóSþ…+KAÇ¿Ðñ/€u@ksõ†›P.Ôéè–m[{�–dð̆é¤>™íkìˆóÓ�×."n~øD}¥ Âì¼0JÁèý¬óxûðõútª©3jo±íÙǼ4ÊÆÌ�ÿ(Ô¶>¼¤î­Ëßò/Ü_ޣ멦äÖëxþ…è”w Cæ],Åòëó‰�áý0ûò 8ûê tO¿ð¸æch¸>’(xÙR|Dðúò§šVùÁa²ñU:ú…–¢£_èèÎáçS'å⪜g²=˜ŒW$ðË5§—gRIBФÈ](È%Vƒ…ÅΰS²-Vù†!üê°q‚b©«õbÒ®SG›qÒŽ ¾Îƒ‡æVÛ5VÁ%Êiä¯ dc&‚ºŠ.´>6Ì‚Ïø­ìô³ˆ¡å8 rW@ 4�ÌP!ò}ò<Ý]ˆ§ýáòÊE'h ŒsU�]«àrÔkŠ OåìH»±IN[t¬2³ûö®´;Qekÿ•¬~?rÏerß¾k�Š3Nàôå,&õ×ß] IìNÒ�ÓILwp­D†b[Å®q×~žÝÝÆþÈh0½¦Z=jîîèÐx=èciRm0qº {–Ìô2���ÕÔ“ý‚”«�A"©ÚÐfVñæò†ðmª®VŽØ]xõÝ@Ÿ‹ýN‹pJÛîœ&±ƒ$Ç tœG“+e|dfø!Jʱ9Œ·•´®—G¬B¯³.ûïa]¬»1zØb§•±Ø’‚–ÕõÆu¢§Íd§±M¦Ó4>–(_,±Aß=4Å ¢`ï Ñ¢8Ê uü8Ì*¼;I–~Ê*ö®=®H\8ÛLHÜû §ÉÉ€f—kz5pJÍŠPínz˲DOÌ%©Š«� —>U[õ£ìëI&0]µêæuzšÎª /F0À‘8z‘<޳5^ºü°•ð@X ¬€æ>­áãäN¹RPŠ‹�  )nk4&[Cäs&¸:Il�&Ö¨ïyjk®µÄ§HD&Ðå‘f㠑褋4.r‡Åtì †Ã÷£ÛšálDñFdÕ•�Óß Ìy2ö·çqu�ê=�ËSÕ»+ò}ý»”K ‡@Zñ%¼Wa8Ï=C�ôถWDŸ¯™ô/7(6” ¿å—›8‰çâ8ͨϾ~Al`LxÀ/6êÂ:ðÀ<‚?x7p#òÁÌ�'´u°žàž~+ª®«©º“Ÿñ.ðøjbÞYXÀî–pøÿq�×Ù7øÃÝÆ^Ëy P΀^K9à«ô³1L®»IW4¶óÎè³>šŸª±ÁŽó÷;×Ȫþ~Õî|¹ÕpÚ™xšRæ2ù·É¯Ôÿ%ªfù^-ðš/›ÔÒ }>Ú�/>ð�¼TÿGíQßKŸ/¨_¾¿G›Oá‘ö¶õ÷ ýcû? ×–�¸¨¦¿MȨWE¸þDµÿ÷Æ+ÍÛÞ«—¹¾±Pþ‡‰ŒŒ†ÈÈø‹Ä˜A åå_ÁöЏ֢Óÿ`Û:ýBõ™.ÉžÀÚ™� úÝ3êë½Æûg`mo5Ù{U@kÑî¯v_(ÿ£Œ÷Whùâk¢™‹–ÿ6--r÷�„fF×i ¥ 1I |Ù�ÛþÊ5¾1>~ý’¡ØãPÕ6œAQ!ànÑl�žÅdC$¬LÞ å}}ÑwVгI‘=2$Ís¬ð´GöÀJÉTHN¸»“Y)O‰¼ŠSÞ·ß¼„œ¨å™±ÿ™ÐJÍU5Óý[h�½ÚÛøoÒxd )s\zêõÙ++9«á%Ðêô6Çe7åá?Ô]¦òÈп¨;}£ü¢ŽTs‹_v£§ÁâÃÔȸÌ�°ÇÁâè­C•|+°øOäô¡5õ2ù÷l °øÃõøö}ïÍ«V`ëæÍÈÜlíÈŒ¿~peV%ŒHMa8…ÚÞæQ�oP(Y9ãe@GÂÝQa±s†tctÛ^蚃äíÀ9¢jøëÀ*en½Xœa™J¥Â߀Å)ˆ'”+,*Û«‚Å šeÞ,^b˜ò³rß-Ó©oÑâeTÃàuêÒ®‘ #Õ3\¾‹I}³÷\?¾…¹V’„·8ž¡…ÕøßAhúp"g{j§Ñ ?U:ÏÅ‚_Æ=ÕöAaw¢Pxë›müÜ߈òl= â`™üboƒwûê4ž‘rH";‚í2ÝŒÑ ñtû.~6:€"Œany“ì…pÍ'<@޳ §z»_FÊ È¾ÉpÏ7T§³‚[Þ�9ªB"šnQކc4 »Eo˜0_E_¿D€¤ÎŠ÷Mðo”ý– :¾ì'\ÿUÊ%†È¸»Â]¥ÍQÖ§â¨@yƳ£5�RÞA¯s1Ïâ¶³ÔH4ò¾~Ƀ§ô÷¹zÉÃ×C‹_/§q³T¢« "žÈý×^:7¿ ZÜö °xÿð`q4«‰¯_þÞÝ ñwBÀ£Ðk£¼CJ– â_ÙÿHJÍÒ„jb}ýâýËÍoFyB7ÿÚ›ÐC¸!ÉܬXY¡þÒÛ§`Š»[$àf©† ´ "p€õý ,x}è–ó)oÖA£.ü–@W3á9‘å]F}?Œ7$ ��€Ý¡D¹¨ÿ»(å)KgÀõ}˜{×\&·Äë_ªk¯ü[tå?ytú¿²»1 ‚ºœ¯A ú[êßìÏÁݳÙVw/àîíñh¨T ¸{wgMGtű¶›A@×µªulìé%×Å4{ñÜÚ¸Õ´Á¸QwÃiöÂÙOûöfžAó(êãÃÝk½�‹Ëow7iY¢–»¶sw?bƬ |‘|9Ü=9ÃÝM3ñT§õÂÙ¡Vgb¯;Ý2�x^RÓ`åÂQ0\ëqTÝôœRkÛ |z×É”bï5À–J½Ñ¦/ödÙW¸±5¬%“)î°Ø~©fkßX€¤ï¬ËÆw;S»žÌÎbJß•:›ép¿¶›åv� 5Jû²T™¸s»ÍJœUÞznHW˳•³»­2¹Ùø|ÿИsæbÙOëNÅ�‰U�,ÜêØÝ(5“é[™ÀêÔ˜Jýõ(À#Rc•F¤:¡æüR„mjp½Ú–ãÒßTýåb€�[kaí„;Ž›H%{¬,˜�ÌWÓª1p2�\#í4Œr×ew} ·DEï�•fgÀl§ª“’]# �pwSm³]ëŽ$µU²-ã ¤cGÄçÓ~M§æ“©—nù?ß6†M3¥¥qh­”'Õ7#šÀ:r½MªòQ«JBsÜqãcÇõwï»õùP«84-b¶ïÜA€J^ã~[Îr8Å[ FÖäÈQ-36×L°dÚ¥:U¥*Ç–ÈíCÑ q+ã%^¨š;õ(»£`Dw�©'4Ku²§W–Ý¡%¶Ù"†ó²DV%sXñCfÜé(ž—Ò•Zw§Áã³&€Ó•AÛ¬L¸ý¬Z°•x뻜DaµÖ&"£D”4ih œí¦¶t»'Ψ£´iÏâz¼ÞJiVä=p'*o±m¾>»ô¬£¬l:,²öbÈ{&)ãûÏ?ƒ¼ß üeÈ{/Åfœ„ç¡Â¼ äª_&ðû:xù^y?¹æ½äýó™8®�x�Ãêƒ�é3„ô‚ÏðQõ=�¼LþÃý’+yŸŸMkûThOê´¯ýf{ò°¯u�˜k…’ß;¦õ¼‘>¥’ß«Ñ^Á›üSêóª=óÂáJ~ïž™~Ä£ ¹¶œ¼QP‡rž=Ë¥Q̱ž÷ø¿jKþ€ô—3ãG]¤~ÁñèJé÷~ËE£}{GaXþ¼—>? 9GÑ>Áÿþ2‡û¡õBI?ØzÌÑ3ˆótÏY!žw‰!*D…aGþÎsÿY—˜7[ì#Çû¼,ïÖ®¸Wê' =Äàð ”q±Ä�+–î¤wçàž�àpù‘9²ñ�¯A-ñ‹ŠxƃÞmÖ2/ŸýÄ@�ÓÌœ¢ºp^=üD÷Û¬þ,_öçZ«úJ½æ Tûúq�¢Úë‹§úë×ê®_PjB�ù.ú9ŠŒ,/ …[(ÿ~p~ñ`}³_¡üG§NW˜1_AùEßÿôäù 5à }Q>} xMêˆbôÿ…Ñÿ [¯ÊR(¿P>Ly¿§º?_,Œ¥Oé®Ðò_•.ªhù¿ÐòŸ± ¾•9ï-•_4ò'ùôüæ³ûBÙO(û*K¹šPCÄ÷;`ûÒ‘?àp\•m°Ä¥RFÏ€º˜b›óo¯=ã�yÚÔA_9IØå>òÓ´gwž§={ócdɶ• ž³$@ æ·ÏÙ‰Q6¤“jàn=?#8J�²¥)¨>� :›äg�LÞNÛ’÷ì]«È6@$†–Ì8cÞŠ7ìw“›¿²_å9#K¤ø{óœ ÷E8¹±Ü»ÑüY{Ã~0ˆ–ù¨WGzëªþ Š ÍŽ�m ”2ý¿ÂiÏC–çŒ*³À#ôz”zºr)86ágNü§èøöŽEKÏÉSÿûÿp(ÞN‡ �…S?q†]Ò„ÁèòKX郱„¡¹JÁV°„,aÿ+ýͬ` ûãYÂøõ±1>,‡-žo¬Dàsé#N—E³}ìRÊA#F»ù�¡µé!žHÜ€:òÃeˆ8Û<`ÄZ‡Á¤c†¼Üîå¼=A¯Y�±Ý\jG pnEVÞ1Xê„Óven„]=ªTð�‹ù$F�Q5©FõÆïî‰ùêÈŒGJ2‹·Øo– ÌÏ(büº¢DV«áW#Fv&X¼\Ó´vÜëÇ© -¬Ž Ì�nLk3¼E!;-FÑBQX~Ãê Y©w˜jŸÖ¼`åÙ9÷QiÚ=kcˆÇcgµÅ+õ�giTc.×Äœ�‰`hCJH"–ÒU¹•´8<ºœ‚áG¹¿]î*äb@íÍ:�ôŒé· iÖp<íñJ=® @š$cjª­aMÌ‹|lÚ4ºÂ¯ø7@Gy;í Š9,fó-ðãÅr‹Ƭċ-y·Úq–9PÚ ÃÄSˆÍªÚé43�¤[fwÓÑÒ¡j”¦ï·fïŒc)Ô–D¬l�qÍYÃ"ü©"²rZNöZiÖXÓ›ò¤Ðoaq¬½jTÉ›^ªªôNwö®uÌJ /«RµªÅ=Q“Íã)‘Wií�¶’-º}ŒU®Þè¯�ázæô™²©Ø¶Ûã~?%úm>ËáBÙ‘{LŒÅÚa¶%—ú$Ýñ~¥2KÙØ­Ø�m@Ù€.쨴df€WÙÞnéT›@«7áâ£jO*B·UYë½y” ‹­akY ¢šâÁmµ_sC½ÌŽó©0§õjwÈ+nâ}³Ž‡ŸqJ¹ïŸO;ñÏq ¼CøöìQE£½l´W€‹J.(Ÿ#Ãï/8€_ÁûÿS¶d´Ü{ZÝ?’"ì¿ï¥Ï?’"ìëóCQ„}b=|(аO¬‡�Eö™ñŒ!YVÁÌŠþŸ ‚?i Ë)¼ֳ…öO²üYnàÏQ„¹ªfºñû[ìÞKxaß>×ñç†*õÏ8c¾™Ñí 6¾+ ?UÔ'Vþ3ãÄ[x åž°+(¿èû?=K…êBuÞ±A~ý¢©º³Š‚­o�wÀ…Þ€�Ó�>s�ÿ)„y1ú¿„Õù £ÿ£ŠúÄ£ÿ¶w å”Ñ¿P~ÑòÑâÿ½\îÞ’*ªó_2æ_Ááã-•_Xvž Žº‚žß|}W(û e_c"O]‹'ì�Ü—±S@Œ`½�q¢F ¬ªm€/ 2“Ûœ}ãïF Àâ:'œ:§­ûÆ]Ê|a}ïqÚB_�‘�å¨xú%ä¬<âÄNpú8ƒ5ÖíoÂ½Š“,2¤£Ÿ�M=F’S~´Ñ•et5FQ�€DŠ$9¢Œ^¥Çe¶”˜Ü†«žŠ$&A×i:{ÛÙ¶Ñý©$ BáŸo»æP$ç3˳P TÀ±§·Ë Hœ®¶Ivzb°ô¢âPÕÁ‚AT+·À´‚øœàŽkûæÀNtÈe©|&…ÈË�9z"N�ìÙz­ò?ÿÿPK!0ÝC)¨¤word/theme/theme1.xmlìYOoÛ6¿Øw toc'vuŠØ±›-MÄn‡i‰–ØP¢@ÒI}Úã€úa‡Øm‡a[�Ø¥û4Ù:lЯ°GR’ÅX^’6ØŠ­>$ùãûÿ©«×îÇ !)OÚ^ýrÍC$ñy@“°íÝö/­yH*œ˜ñ„´½)‘Þµ�÷ß»Š×UDb‚`}"×qÛ‹”J×—–¤ÃX^æ)I`nÌEŒ¼Šp)øèÆli¹V[]Š1M<”àÈÞ�©OÐP“ô6râ=¯‰’zÀgb Ig…Áu��SÙebÖö€OÀ�†ä¾òÃRÁDÛ«™Ÿ·´qu ¯g‹˜Z°¶´®o~ÙºlAp°lxŠpT0­÷­+[}`j×ëõº½zAϰV–2ÍF­ÞÉi–@öqžv·Ö¬5\|‰þʜ̭N§Óle²X¢dsøµÚjcsÙÁ�Å7çð�Îf·»êà ÈâWçðý+­Õ†‹7 ˆÑä`­ÚïgÔ È˜³íJøÀ×j|†‚h(¢K³óD-еß㢠dXÑ©iJÆØ‡(îâx$(Ö ð:Á¥;ä˹!Í I_ÐTµ½S 1£÷êù÷¯ž?EÇž?øéøáÃã?ZBΪmœ„åU/¿ýìÏÇ£?ž~óòÑÕxYÆÿúÃ'¿üüy5Òg&΋/ŸüöìÉ‹¯>ýý»GðM�Geø�ÆD¢›äíó3Vq%'#q¾ÃÓòŠÍ$”8ÁšKýžŠôÍ)f™w9:ĵàå£ x}rÏx‰‰¢œw¢ØîrÎ:\TZaGó*™y8IÂjæbRÆíc|XÅ»‹Ç¿½I u3KGñnD1÷NIBÒsü€� íîRêØu—ú‚K>Vè.EL+M2¤#'šf‹¶i ~™Vé þvl³{u8«Òz‹ºHÈ Ì*„æ˜ñ:ž(W‘☕ ~«¨JÈÁTøe\O*ðtHG½€HYµæ–}KNßÁP±*ݾ˦±‹ŠTѼ�9/#·øA7ÂqZ…Ð$*c?�¢íqUßån†èwðNºû%Ž»O¯·ièˆ4 =3¾¼N¸¿ƒ)cbJ u§VÇ4ù»ÂÍ(TnËáâ 7”Ê_?®�ûm-Ù›°{UåÌö‰B½w²ÒzÏû¨nœ”ÇÊœ"Z úàxŠÕJÜZšìp;‹“Êì ØåÞ{/å<óP;™Ž,)''KÐQÛk5—›òqÚöÆpN†Ç8¯KÝGbÂe“¯„ ûS“ÙdùÌ›­\17 êpõaí>§°SR!Õ–‘ 3•…K4'+ÿrÌzQ TT£³I±²Áð¯Ivt]KÆc⫲³K#Úvö5+¥|¢ˆDÁ±‰ØÇà~ª O@%\w˜Š _ànN[ÛL¹Å9Kºò�˜ÁÙqÌÒgåV§hžÉn R!ƒy+‰ºUÊn”;¿*&å/H•rÿÏTÑû Ü>¬Ú>\ Œt¦´=.TÄ¡ ¥õûS; Zà~¦!¨à‚ÚüäPÿ·9gi˜´†C¤Ú§!ö# Bö ,™è;…X=Û»,I–2UW¦Vì9$l¨kàªÞÛ=A¨›j’•ƒ;î{–A£P79å|s*Y±÷Úø§;›Ì ”[‡MC“Û¿±hf»ª]o–ç{oY=1k³yV³ÒVÐÊÒþ5E8çVk+ÖœÆËÍ\8ðâ¼Æ0X4D)Ü!!ýö?*|f¿vè uÈ÷¡¶"øx¡‰AØ@T_²�ÒÒŽ q²ƒ6˜4)kÚ¬uÒVË7ë ît ¾'Œ­%;‹¿Ïiì¢9sÙ9¹x‘ÆÎ,ìØÚŽ-45xödŠÂÐ8?ÈǘÏdå/Y|t½ß &LILð�J`è¡& ù-G³tã/ÿÿPK!2Õ¡Ý% word/settings.xml´VmoÛ6þ>`ÿÁÐç9–,ÉŽ…8ElG]‹d¦ìPmáHÊŠûëwŨ^Ô X±O"ï¹÷;ÞéæÃ £“V𾢫0˜`^‰šðÃ:øû)Ÿ^m¯¯ƒ3ÖÁ‡Û_¹i3��6=\g¬ZGcd6›éêˆÒWBbà^(† \ÕaÆ�znä´L"CJB‰9Ïæa¸z5b4Šg½Š)#•Zì�ÉÄ~O*ܼ„ú»Nr'ª†an:‹3…)ø ¸>©½6ö_µAˆG¯äô^'F=_…ïqöá¶BÕ¯?âž�JTXk(£.\†U%o½¦ú R=s¶gVˆGaw<×ô�üHµ]H©�re†°^°*ûtàB¡’BSµQÜBG}‚MÚLbUA‘ Ó0˜Y‚û ƒÖSÚõgE1⎣Æ{ÔPó„Ê \'.罂ꈪ V…DÈn7JPÏW‹?„ÙBO*H™Sè:Ôw§Âu;HpÄÀgGí;øQÔ8¨QäMZ¾›V+Ðy ÑwQŽð:©1„FqaÎçà|A¾â;^n´!ð&º>þ Þsskù ¼å§³Ä9F¦�4ýOƺJä”ÈG¢”PŸx �ð³Æf¾ˆ¶œ0êjí a|`ÅË»ÝÎå² È|•,·}/]"qœlÓë1™$œ/ÒÅ(’„ÛÍ|I“û<CÒ(¹[õ�réAÏw«Q¯Óe´ÚŒËäiÆcv¾Ÿƒë(Ù.ò1™Í2 £Ñx6«$½ÍÁ.�ÓxTÛnÆ«Qm»û$G«�Ï—‹|;æ[ž&w×÷�.èkÏ2;¬ÿT·7îdÔ„¹Ç¸E¬TMí8)–•êyC¸ÇK ë ‹MéÁéÔš!Js˜8èœfYM´Üá}§–>"uôöj” Óíó«.;±ú¨D#�µV!éŠ7%I¯�pó@˜§ë¦,¼‡‘ü ÔðúËIY…³!=mf`“wçñƒ˜O?n,+¼+ª »íñ#’+°”‡hPr8šÈ ·¶~w)ó›wÜ,Ö]Pe#îþ`ܸúÃ@‹=-h°Ó_2ÐROKÚÂÓ௣͎0Õ,”gÝþhé{A©hqý»'®ƒ7$—}DC]í‚Ñ"²ŽÐ¯$=9eø¶®‰�Ÿ)Ij†^`¹Áx°â=7Egј ^‹YfyA�ÔÈ ïJu!ܵø¿|i³WÚ±8³rXxWÎqJ´)°„Ýh„‚�»uô[§yø¿»ýÿÿPK!abü» eGword/stylesWithEffects.xmlä\[s£¸~ߪó(Þ3¾ÆNRëÙÊeg’ª™Ùì89烫ˆOæ×o«2-Ã<�§!õ×­îþZqÔùý��o½±8á"\Ø£CÛb¡+<¾,ìç§Og¶•¤Nè9¾ÙÂ~g‰ýÇÇÿüöûî*Iß}–X L®v‘»°7i] ‰»a�“|¸‹D¬Ó®b½æ.ìDì ÆÃÑ?E±pY’Ú­¾9‰�‰ ªÒDÄBÀZ‹8pÒ䃈_�¿n£3�9)_qŸ§ï {8Ëň…½�ëL¡3­�\r¥Ê~ä+âŠGpÕÊ;án¦ˆ8ˆ™:ˆ0ÙðhoÆ©ÒÀÄM®Ò[“o�ŸÏÛE£iO›LñÁ]ììÀ{�qG6ÃS‹_íƒôïÞ«e‰£a“1™G¤­E…CÌ\“Àá¡sÚÖ7ò¡K|ŽÅ6ÒêD¼›´‡ðUË’ii Ùp†™W4-1PIÝ寉˜mîÕÃK(bgåƒF»ÑÔ’iªð„{ÇÖÎÖOù?ÆÙcö„?>‰0M¬Ý•“¸œ?…€”€ƒÀûë0á6¼aN’^'Ü9úr#g}ã&iAÚ ÷¸=�ˆÉO�ùæø {<ÎGn¥c¾¾äc,<û|SÔdaÃÐóR­@îÂvâ³åµ6@3óŸs£ãá U‰2Ä8ë” ‹I¡>—ÞÏ�ÑÔÃ÷­Ü\g›Š XQ,<–v¸ ˜j©Þ²õá¾2o™Â‹…�X0øüðs�.ìËK‰ ƒKð{îyLˆlì9Üp�ýoÃÂç„yûñ¿?!=g]± SP6Ç(ðïÏ.‹$M‚èБþ&‡�; 8¨Ð–ïµQ%Tü'‡)EÙ0G–4 õoB«·��ÆÒ¢¢(×H×IwÓî"λ‹Ààí¶óîZÀA¦«GTl¢’îÔT¸*øŠû0¹lY¹¢E­+*AÓº¢#­+*!Ѻ¢­+*o]Qño늊;W¸W9Š&¸¤Ä~â©u²…éF©.+5Ö£;/±m,YXËj7‘år»Jiª"�žN–Ë4ò¸Ù²#P�eêžÌÉÑÆI8œÊÛ€:ný“<úXŸcÇרs|›ð`r´„=úŽË6Â÷Xl=±Ê£ë¿ k©N­Êutëþ²I-8Ê’Û 6«Ùôú�Pò¿ð÷ ±šÏjLiNòá¬&.ë…eßùÖN#3Åçn.A ŠÍ[4•.ªfW«ÒT¹07åôWÅÅ\¾ô1EUŠN”OÐ_®åc|4ûטiîàk‹”^sãܽ¾ˆ×[?Ï�Vz˜g°† ™`œÄZ>‰$æÆ|@ŸÖµëÂon”85öÅžG PŒÝ¡P0Ùè¶;¥D{#‹ŒTÂ`uãZ cÒýÎÞ¸üØ´ Kë³fk:OjvJé ý÷V¤ígèq çQQBøº$a mR“yT´,žT½3ðq·ÂgÔ­u+…@5ñQæÑ5‘Ò½8`Ó²®bvdfž3³2+=ÕMÂù«&{ëc¡Z7 (ƪÖMбwJµL×MVou“€US5ê}TäT£ŒëfHŸõCÞ ~È›Ôy€º“w;HäMÀ2æÍ©Eò&á“_õ5P‘¼ @ÆÜ Ø.ûÎ(¯{(¥ù—ÛÈ›€bì *yPŒ½SGÞ,œb %,Mu¬~È›Ôy€ú!oP?äMꇼ @ÝÉ»¤?ò&`sƒæÔ"y€ŒéAÉ›„SL¸á(ycÖÿrò& ;¨JÞcï”UR XÆ*aiò&`á“`Ȱ0¸MŒê‡¼ õCÞ ~È›Ôy€º“w;HäMÀ2æÍ©Eò&Óƒ*’7ȘŽ’7&ã/'oбƒªäM@1öN‰P5ϰŒTÂÒäMÀÂxéLÞ œr*�‰Eý�7Á¢~È›Ôy€º“w;HäMÀ2æÍ©Eò&Óƒ*’7ȘŽ’7æÈ/'oбƒªäM@1öN‰P5y°ŒTÂÒTGÀꇼ @˜�É›„SNÂ,2qS?äM°¨ò&u'ïv�þÈ›€eÌ šS‹äM2¦ T$o�17È{¶p_”|=uTÔ{ù­2à¸ÆITÀÌÀïlÍbè*dí·C:æ Ö„ÕÄ!^-ÚÅîIM€�¡øÊç¯t¿ã-�B#ÂdÞÐIðô×­u¯`*ë0¤oÞ@÷P±]Û“dã虾Gвå7Ë¥4h’}]Y ö„>@CPÖÖ#Ë>˜ˆMUÙ0þÝ6C…Ï€ˆ «Pî°\èˆj€Ê.¼ë;Hxݽ \s+Ù·däjf·ã÷g(5ïàŽf£Þ©¼ Þ 3ÞoÜ# §(¯V„æ,T©MCpÙÊW-fðá!ôÀÂ]Ö�¥œéýp”(xË|ÿ«ƒ i©ˆê§úl�ª·£!VÀ’¨•HSÔ¯�ñ‚8jrL„CQõ(�¨�“p¬Xœ]7¯ IY9°í0$Õ]ךP ît½n:tr keu©(ƒoTê³r ½î/Ù-‡éƒ¯‹9T� h,À•ÐÑ-��Ë #z2¿¾»SoêZñO©YÃáT?o8<Œ«hØ…r™s*nUvugý.?¶úê òvCWÞÞ·2騨:i­Ž¸“VçñxÒbm§»Ï÷ÚÔjµü¿§-W©¡·Ÿ’)ä­#ò›È{CÊÄYh©É�Œ,÷t�¯Á7™LoÏ/ú ¾úT;nÜýÓ×/�1Sÿ9 eÕ¬“¬ƒÇ’¯˜w�&¯¹ËÊâo¡f)c«ÙIe“òÞÝ '—Y“r–¿@+*ãœU®‰Ì Yt"Ý—£™Ò&�0º˜dëfŒçÓÌmu3&³Ù´ez~1lžq>½lÑt6µh:ŸŒ[4½O[4… kÑt4B'6ÆF݆Œ†—¹Ÿj§Œ.¡j6Kƒº-S&s éf)ÓÙ9ª+y!‹–¤Üh®‰ú´ÛY:÷%AĽú ûVlc}iߨNÆ_Þ§¿°Ÿxÿ"†­ï"pðr9öéW–¸±E)hX¡A?35ùYhÐÏÆ²ý=¨êÒ `7*\OçKw›À±e)ϺåãìÑ/Se…F¬=” ùÑ3+ÚÜÄ,ÊÝdV9dá"…ü¿º1òÏ’QåÐ¥Fq÷ÉnªÛÛ}¨åŸ’�ÿÿÿPK!t?9zÂ(customXml/_rels/item1.xml.rels ¢( „ÏÁŠ1 à»à;”Ü�Îx‘éxY¼‰¸àµt23ÅiSš(úöO+,ì1 ùþ¤Ý?¬î˜ÙS4ÐT5(ŒŽzG?çïÕ‹�½�)¢�'2ì»å¢=ál¥,ñä«¢D60‰¤�Öì& –+JËd ¬”2�:Ywµ#êu]otþm@÷aªCo úÔù™Jòÿ6 ƒwøEî0ÊÚÝX(\Â|Ì”¸È6�(¼`x·šªÜ ºkõÇÝ ÿÿPK!¸5®]9 tDword/styles.xmlä\KsÛ8¾oÕþ–l×(S~LÆ®J<žÈÞ=S$d¡BZ’Šãüúi4Hˆ"²!2§=E�þºÑÝ_C2:¿ýþ# �ï,I¹ˆîèÃÐuX싀ǯ ÷åùÓم뤙^(b¶pßYêþþñßÿúíí*ÍÞC–: N¯"án²l{5¤þ†E^úAlY /×"‰¼ “×Aä%ßvÛ3_D[/ã+òì}0gn.&¡Hë5÷Ù�ðw‹3\?HXEœnø6-¤½Q¤½‰$Ø&Âgi FG¡’y<ÖbFÓš ˆû‰HÅ:ûÆ ”F) –�†ø) ]'ò¯^c‘x«6ïm4u?Âοckof©|Lž’ü1Â>‰8K�·+/õ9†-Y÷×qÊ]xü4»N¹wôåFÎ:úÆO³’´pw ÓŸ ó».Üñ¸¹•Œ…^üZŒ±øìÏ›²& †^–rhr®—œ-¯¥°šYü[2w{`<<¡*[Ïg€o�1 ˆ)4ä2Çsˆõðu'÷ÕÛe"AV �•‡X�ÈYª†·lýYøßX°ÌàÅÂE,|yxJ¸H Hîå¥Ä„Á%‹ø=&ó%{‰7<`ÿݰø%eÁ~üïOü¹D_ìâ ÔŸÍ1 Â4øã‡Ï¶2lAtìI?Ê8àŽ*´ã{mÔ@ÿW@Ž”�¢l˜'3ÜAý�Ðê]g ±´¨lʵÒuÒ]Ä´»ˆóî"0x»íż»Àë]=¢b£•t§fÂWÁWÞ‡ÉeCÈʵ(j]Q šÖµi]Q ‰Öµh]Qsx늚[WÔÜÙ¸Â÷�¸ªQ4ÁÝ %ö3ÏB&×7Ш#Õ奯yòï5ñ¶GÖªÚMd¹Ü­2šªH§§“å2KDüÚº#P�eêžÌÉDÛ�—r8%µlý¸ãÖ?ËS�ógƒV¨s|5›ð`r´„=…žÏ6" Xâ<³Ê£ë…³T§ŒVå:ºõ3ÝdÎrƒ%·lfØtóN(ùŸyŠ{ИL3ƒ)mÂI>œâÒ,ü ø.*¶†p™)>·psUlÞ¢©tQ=»Z­� ˜ Ê…½ (Ÿ ¿*.öò¥�)ú«Rt¢|‚þªp�(ã£Ù¿ÖLs_ZRzÍ­s÷V„"YïÂ"Zéan�Á‚f‚ukù$’˜[gð}:×¾ßÜ(qjí‹=�Z X»C¡`²Ñm±vJ…öFY;¨‚5¶ÀêÆµ@Ö¤û•}çò71Ûb€,­Ïš­é<1ì” Òúï�ÈÚÏÐcçQQbø¹$e mbÈ<*ZOªÞYø¸[á³êV-€º•B C|˜Ï<º&ÒAºG ,kZÖU ÃŽÌÌskfÖ@v% §ºI8²× õºI@±vP½nP¬½S©eºn°z«›,CÕ0û¨Ì©6FY×Í2�> ,ꇼ @ý�7¨ò&u'ïv�þÈ›€eÍ šSËäMÂ)6_õ5P™¼ @ÖÜ Ø.ÿͨ¨{(¥ùËmäM@±vP�¼ (ÖÞ1‘7 §ØDBKS«ò&õCÞ ~È›Ôy€ú!oPwòné�¼ XÖÜ 9µLÞ kzÐ@eò&án8JÞ˜õ¿œ¼ (Öª“7ÅÚ;BÕ‡T–µƒ*Xš¼ X8Å&r, n£ú!o‚Eý�7¨ò&õCÞ îäÝÒy°¬¹Asj™¼ @Öô �ÊäM²æ†£ä�ÉøËÉ›€bí :yP¬½S!TÍs,kU°4y°0^:“7§œ dcQ?äM°¨ò&õCÞ îäÝÒy°¬¹Asj™¼ @Öô �ÊäM²æ†£ä�9òËÉ›€bí :yP¬½S!TMÞ,kU°4Õ°ú!ofgò&ᔀ0‹lÜÔy,ꇼ @ÝÉ»¤?ò&`YsƒæÔ2y€¬éA•É›dÍ òž-Ü%_O‚€zÏ ¸Õ@œDÌ üÊÖ,�&+Ö~;¤#`a¡¢!<¨&ÞñÍ¡]ìž„ ÅW!x¥ûoé”&ó†N‚ç¿n�{ÕS[‡!uxóº‡ÊíBØž$‡@Ïì} -;Ûâf¹” B²¯+o¹hÊÛzäbÙ籩*Æ¿Ûæ¨ðqaÊß–Q Pù…w} ¯»W� ·âQ‘}KF¡f~;~†Róîh6ê�É›à :ãMñÆ=rpŠòj]AhÎB•Ú4—­BÕbâ,„&Aü«™rfðÃS¢àý- Ã/6¤ebkž²u¦ÞކX+¢V"ËDd^ŸàqÔ䘇²2êQaŽ“x­X^ {þ(dåÀN´Ã�Tw] ¡@Ýi³n:tr keu©)ƒoTê³ò ½î/Ù-‡éƒ¯Ë9T� h,À•Ðà*��Ë ßt2¿¾»SoL-‡yÃáT?o8<Œ«h…ŽZ™s*nUv—æý.?®úé ŠvC_ÞÞ·2騨:i­Ž¸“VñxÒbm§»/öÚÖjµü?§-W©¡·Ÿ’)ä­#òQ½!Uâ,µ�2$'Ë=�CçëAðM&ÓÛó‹þ‚ÏœjÇ�»þòù)aª/;cõ¬“œƒÇ’¯œw�&ß —UÅßBÍRÆÖ³“Ê&Õ-¼»N.ó&å<�VTÆy«B™A’·"…þÝÑLi�N]Lr‰¦ãù4w›iÆd6›6£LÏ/†Í3Χ—-šÎ¦£Mç“q‹¦ãi‹¦°a-šŽ†CèÄÆØ0mÈhxYøÉ8et U³YÊÔm™2™I7K™ÎÎQ]É y´¤ÕFsMôЧÝÎúй/ "9èÕ_¸·b—pèK{do2þŠ>ý…ûÌ#ø`Øù*"/—cŸ~m‰[–‚†•ôsSÓŸ¥ý|,oÐ߃ª.}�v£ÂfÂ88_ú»Ž-KyÖ­g�æx•*k4âì© RÈ�žYÑæ&fQî&³Ê! —)äÿÕ�Ûð,Õ]jwŸì&ÓÞîC­ø”~üÿÿPK!tz+ãßU(customXml/itemProps1.xml ¢$(  œ�ÁjÄ †ï…}™»«6&›.1ËBXØki¡WטDˆÔ”–Òw¯¡§í±§á›aæû™æôagô®C4Þ `{ H;å{ãF¯/\ŠIº^ÎÞiÎéÝ=4}<ö2ɘ|Ðפ-Ê “ëµðÅ9?Ó¢(ñ…˜SVãšRŽYÉ ^°®zªÊo@Yíò™(`Ji9Õ¤­Œ{¿h—‡ƒV¦Œa$~ŒÒ�W«Õ.‘GJ+¢Ö¬·ov†vËó»ý¬‡x�[´5˜ÿZnæ6?¹LŸ@Ú†üQm|÷ŠöÿÿPK!'¡¾±RˆdocProps/core.xml ¢( „’QKÃ0…ßÿCÉ{›¦u:BÛ�Ž=9œ(¾…än 6iHâºý{Ón«u >¦çÜ/çÜ´˜íUíÀ:Ùè‘$EhÞ©7%zY-â)ŠœgZ°ºÑP¢84«®¯ n(o,<ÙÆ€õ\HÚQnJ´õÞPŒß‚b. Äucóáh7Ø0þÁ6€³4½Å <Ì3Üc3Ñ )ø€4Ÿ¶î‚c¨A�ö“„ào¯«ÜŸ½2r*é&t:ų?Šƒ{ïä`lÛ6ió>FÈOðÛòñ¹¯KÝ튪 Á)·À|c«9ÓêhÉ´kt�GJ·Åš9¿ _K÷‡KóoC7ca'»«ÈM�Ççpmßòx7ˆ(ä¦Ç–gå5˜¯¨ÊR2‰ÓIœMW$£„Ð4}ïÂý˜ïz?¨Sĉw=1§ùñ ¨úÄ?ÿ�ê ÿÿPK!©È\ªŒÚ(customXml/item1.xml ¢$(  ²I² Î/-JN-VNÍIM.IM .©ÌIµUŠq pÔ‹öQR ø%æ�bJ ¹9yÅVI¶J%%VúúÅÉ©¹‰Åzù©y@¹´ü¢ÜÄ ·(]??--39Õ%?¹475¯DßÈÀÀL?)3)'3?½(± £jUŒ²³Ñ‡{ÆŽ— ÿÿPK! 8¨·word/fontTable.xml´”ÛŽÚ0†ï+í;D¾_â„ì�hÃjK‹Ô›^T»`ŒÖúy YÞ¾;d/•´j�¢0cÿšùôÏ<=h•ì…iME² %‰0Ü®¥ÙTäíuyûHð̬™²FTä €<Ïo¾<µem�‡ï(5¯ÈÖû¦LSà[¡Ll# &kë4óø×mRÍÜû®¹åV7ÌË•TÒÒœÒ{Ò˸kTl]K.¾Y¾ÓÂøp?uB¡¢5°• ÕÚkÔZëÖ�³\`ÏZE=ͤd²âDHKî,ØÚO°™4V”vRx=£áK+’h^þØëØJ!»6+ȼ—´¥aƒ ¦äÊÉ�h˜± 2Ì홪Íé’Þá»ûtÚ½IÚ)ð-s üp�ÆpÍ´T‡cZ �ô|{Œï™“]A1rƒ‰¬hE¾S|òå’ÄHV‘/‹!’cQñÉú3Ó!‚ÎÁ‚N8’Í‚FP§¿êL£uNH¼J- ù)Úä—ÕÌ\ ’Ó{$q‡<:2ÓQD\Ð GÉ_†þ±“¶òðXûÿ$2û3‘¨s=‘…Ý9)\Çä�$0 þèh£hh»Μ1H-?Äú¼;β˜ö�ÿg¬bpÄW´E1~PFƒÈÐ'c²<7&W˜bì˜,˜Æ}Á.pèÆ"ŽG7&ãÆß�Ç) Z ói‰°pÍüËÂè7ÌÿÿPK!‡¸Ñ¬[word/webSettings.xmlìXÛŽÚ0}¯Ô@~_b“;ZX‰®¶ª´½¨Ýö=$¬ÚžÈ6¤ì×w’ÀV‰ò”§ø6ûœ±ÏØ·w”ì­¸±ôˆ°>%=®s(„ž�Èϧ‡›„ô¬Ët‘IÐ|DÖÜ’»ñûw·Õ°âÓÜ9i{èEÛ¡ÊGdá\9ô<›/¸ÊlJ®±sFe«fî©Ìü^–79¨2sb*¤pko@iD6nÌ)^`69¿‡|©¸v�½g¸D� íB”vë­:Å[¦( äÜZ\�’­?• ý↎”È X˜¹>.ÆkgäծМѦ¤$é©|øi®ÁdS‰V, c„¯+»ùöª¡(F$ Ÿ â„6ýS(Ö÷b…}«L"5Ä«G#x�|æ¶­ô¥õ»˜/Ž4?Ay8vÎ�zÕŽó™¦þ‡ûg£‘t‚íóˆ`h`¡Ìr\DSÎAr•-´Ó�;3;Ïrº7£ólÍîÊÏ1õ6‹®éø°²Øç„l¥!Mý†”þÒ/ËÄ+äc?Ši¤ívè�¿òQ¤Iì'ióǺKÆ|ûÛ³çèNˆB¦1£¬†säè,µ:}L(M耥~Ôm’ÿ¿I0ý¨Ó$æÇ~ P™;䯌< "å° ù7àKœ8[8A’$ÑöBÑ]N¼¦\‚¥·u� X$qØ’Ò¥­WK[cÒ$¦¬»©]átÚd–D!‚ß…ü�?® ›Öú ¥J<ó0•å¦y9ÂDZõWýëócSˤ„êÛ—�XAÓ�§¼ñ_ÿÿPK!pµ|äådocProps/app.xml ¢( œSÁnÛ0 ½Ø?¾7JÚ¤ÛFÅ�bèa[ÄmÏšL'ÂlI�X£Ù×�²ÇÙvšO��4õôHÁÝ[Sg-†hœ]å³É4ÏÐjW»[åOÅ—«�yIÙRÕÎâ*?`Ìïäûw° Îc ƒ1ã6®ò=‘_ õ'œ¶œ©\hqvÂU•Ñxïôkƒ–Äõtz+ð�ЖX^ù¡aÞw\¶ô¿MK§“¾ø\< –P`ãkE(¿'9õ¤tÔ€X(©º0 ÊÛ)óCµÃ(g z/.”Q~ø¢G°Þ« 4±ƒr¾X€ÅðÙûÚhEì­üftpÑU”=v.déã`g¶¨_ƒ¡ƒdã¾ËBn@ô€…µ Êï�ꆶZÕ¸æëËJÕAœ x@•F»Q†õBKË5¹�Eó‹‡{�g?TÄdÚ*oU0Ê›—Êú Ãµ�da¨æÞœëãŽËÆØÌ“…\Ëà²0‘½N\ªëNˆ�ß”þ!v6Ûi襎äŒàpÆ]×®ñÊÎãá ©dùÏøä wŸ6çhæ%9ÿ‹¡ýÖ+ÍSZÜðœÎ‹0ÊÀ–×Kžì©ß™€ö=ÔéPþ×î°<Õü�H«õÜ¿Z9›O¦üu»tâx_‡ç$ÿÿPK-!)Âeœ[Content_Types].xmlPK-!‘·óN Õ_rels/.relsPK-!ø�4Gùword/_rels/document.xml.relsPK-!{×+Qbʆo word/document.xmlPK-!0ÝC)¨¤ïkword/theme/theme1.xmlPK-!2Õ¡Ý% Êrword/settings.xmlPK-!abü» eGÖvword/stylesWithEffects.xmlPK-!t?9zÂ(É€customXml/_rels/item1.xml.relsPK-!¸5®]9 tDÏ‚word/styles.xmlPK-!tz+ãßU5ŒcustomXml/itemProps1.xmlPK-!'¡¾±Rˆr�docProps/core.xmlPK-!©È\ªŒÚû�customXml/item1.xmlPK-! 8¨·à�word/fontTable.xmlPK-!‡¸Ñ¬[ “word/webSettings.xmlPK-!pµ|äå­•docProps/app.xmlPKÜǘnumpy-groupies-0.11.3/diagrams/label_contiguous_1d.png000066400000000000000000000210221501360642500230150ustar00rootroot00000000000000‰PNG  IHDRò•û{ÁGsRGB®ÎégAMA±� üa pHYsÂÂ(J€!§IDATx^íÝolç�àñŸ®}´ˆÜ¢¨ý"€½[“2�ú�v£¥Šuá(ÉIºäŒ"Õ&Ŧ¶�DL¼ÀREœ]w#-Ú³sñ!)N\KNûZÀ9�Q¸ÎJjmÇÛ�Ò5R\©îÙ@[È@€Ø~ѼYðžßÌ3äp8”HY9~¾àg†Íü2žß<¦£dH„ÿd_@�È� $ò$‰< B"�¶WÌ÷HGGG¥dgì;P3Y—ž¼í6%ßÚgLéÉs0IO(&Ä%^øœÃéfF²¡ý¥ªpÎñÍd#±Éš¨¹ªöÜ[SœÞoââÓ#œ†×í°Fëv®Õ‘›©óºP?&‘G[Óƒ0=5S·s©ŽÜl\\©“È£}™„Ì?‡dºT}Rb©4mÖŒ¹aÙãø�Z?6+ؘÀ×·ÛÄCãì3¦LÛMœq¸¥ÌÈŒI¡|,™R3‘2\�K™IÌöèÍŽ!ó"+3V¨ì7A™ÍIʾ¬M<¢ÇUi\úü�8(%¹Ùp,BÅž‡3ƒNî7ÅüaÑËvͱdã2qÆÝ³ðL¶?&6ñNÇ�é±ÐTÝΡ:r3qq­~L"�¶5sÆ;­›1\)ê“q›|ÌMN9Û*ß·Ûœš†¦ÍI{Vr.פc™}$—¾qñëJ—eÁÙû?&.ÑÄ+•“‘{ý*ׄb~�—˜ M�Ë€ÝÄ*æå°^¢ô<ìø �Æ%ïLF¿hmïŠü÷{7Ÿ]6#~uoLNVí)É�ÔúÞœLN¹qán¦nçR¹™¸¸V?&‘G› Nìƒ2Pu -fÆÜ)xÛdÓÒ¸»m>X#öŽ~fì¥PEÀQ¶K½¶qha%Å©Is2•è—ØY2óšw“ÌåsMj`ÐK°&úÃc¿µK¹¶F³/Éö®Úb©.Ùn^æ®8RÛk¸nçX¹™:¯cõcy´§â‚\Ö×ЉÝóCårë*šSï¢ç;¦W'Ñb²ø¡é’Ì:ÞBV®üÔ´!à�U öS\Ÿ$±pE/DÛ¥«�莉ËjÑïIåd6h--OšÄk×h—{×¥åAÿGdž½^ù]îåò‚YCudX$òhk™ÓÌÈé�yÑ;ú)0~MðÇßedì$]`£ª[‡Üäw©gÿh†—ˆ8;éhQ¼Zô„ô¦2&.«Ak|Ej@kª/®'\)ÉÙ1^z=ªÜ‹›aÔ‘A"�¶¦•ÅòŒœÞ˜¿õÐo VL|24ír‹G :•?ŒgÎ&å.õ'Ù?béœá}¦²ß˜ÇéIG=öº” íòÄeÕh�¯¨ÌÌLêæÏÝÂͯ;t0)m™Žÿ¶çš¸n÷ Ž y´);.ÊÌ\ó´†l—èœ1@X%‰/1î9VebœËŽ6 ùc�ý Q¥%¨òHŸ …ˆç퇙ý¦¦Òí ½6EN,©ÜI¦mºûlk¼ ív¾5><3{0œ©oÜÔm‚Yë;þX]Mæí 1¿ÌJNü.äÚòŒêȰHäѦ옩¸îÐÅ)™ÔŠAæAó) Ž?œ$¸;Š~MÑQ)éòfàšG&Ó^¥ 5ž‰Ü*ÉUF£µ³Ö»<�o3¯éÄm11sudøHäѦR2à $Ó.gY©ô8›‘lÚŸ‘sh„ñ¬ˆ£I¼ß}‘$~%ÁñänE)•›�´ù%è"­û�®3!`ÈLÖöXpw¿ñq¤×§=Õ­¨Á³å]}Vz•rküÃV‚›?fŸ©y”ÚÌB7¯†`Ç}³ÿÄ Ž ËTP€6U(™ºtIwÓšbj×N›Š�KP2c&zn2 X|L‚âjl–Ùg2c®î-õû‘ë§šåö·c³ÌõI†J®ï6•ødJœ^¬®ÛNO…±’IKkãâÚõº©º�CuäfâÒT “�y´1b®èPLo’šY�5 �=¢µM²c2Ý> Ç_ŸLZJ¥qfg§5¾–7ÜNÞVÅ?S­ ócRš¥U¹>êÈéÐlÞ.€6G‹< Òx"_Ì‹tt˜Ò£sI€¸Ç[ä‹"=zóÁ”Ê”Ž$Vã‰|*'¢ÃéK³:¿h�{|²;m‘OëcE¦Í&“8®ÊíÛ·í¢–n3W$š³éþ»4î³ú£]p'>ºïÓv hÜ}_´K¨²´duêî·|7ѵ~ÆŽ‘ÏÚu+;Ÿµ}׳¶+{P⺴Gÿ&ßSý7Áö°àozÌk�  }hü¾÷;l¯úíwkÉ3È�Lk8FþŒŸ$OØÕ€&Ðuç?÷62ÑŸÌีKä'l?V°céMò7Éä”]ˆ˜ö[ÌÃ3mÿH“ù;ÉåÇõûÌ÷fìºv­þ�ƒüÉ´¶³Ök²N’ǧý×¹ÉJ—÷*&Ë.Dþ¦o¼rà ­ò„­]"Ÿ‹™L.]i�“ŒŸ·Íä//ø¯pÞâÑ^éì쬔Ü9ûŽÃþßQ|`ƒ¤Ceðøï웎#6ËâxŠG\âüRžß´M>WNÊ¢ý”{ˆK}Ħïæ¶ÊÆ ¦ÕñÞ¨IÜ{å`¤Âwé•^é}Ï®ÝÛÚ3‘Çš¸~ýº]J°s9yò-]Ø'§oÝ’[^9mÖŒK%{ÔáSØ_¾*~S A™}Uºuû�ÿEÜ8}-ƒØÄãxŠG\VôÕCçä?–®V—©=²Õ¾ï*âR±©¥ÉªŸ¨þ…šÿ¡|ÛÛ â² ®Oõíì5qè–# r«`Ë›Oùï½}A\è³Ðž‰|áŠ]@3nÞ¼)Ç�—ŽŽ¯lÙ²Å{}á…da!™ÃÎMéÙk‡™ÏË£þ&ãQÉÏ1[Í9ì'?wônä#rèì~Ùb×<¶_þöoì²ÓˆM=Oñˆ €õððyZdïåÆÍÿ+Ù/Ù� .Ëàú´œ�’/LÊþp…oç!9íåòW¥è@ûÖ&òõ&Á;cgÀð_Ãæb’üâTåyñÓD~hhHžþyyýõ×½¢ËÇŽ“mÛ¶%°…þœøç¯oÈcU·ðåhö x�Œ.]¥‚¸˜“g,Òý½œ9µ¡ ±18žâëä‘Ãr#ÿu»‚2âR×',¯Å-ò&ûNwT?f.ßcŸEŸÍ„—ê² æÍð3æg²æ;†íJ”ùûívÑ�ð7lØ ü±¼ñÆòâ‹/zE—üqïýb±ÞóüÛÔbQ~£¯_N•»âé8¡ÎÎn© ú��=ƒýNN<šÐí™òÔ�nÊäsÜÎ&618žâ—†¼?úhÕ¤e_›pg2¡å—úˆ p‡¸>­Â{2õ¶yéî—ǪºfÞ›Z›Ègì”öý&™ï°eØ6­��ŒÌhßg¶ÙÏë3æƒÏ÷kÖ?Tyd]T0~øoò Kh¤‰¼–¨�;ý6ÈÅÅdé;¶m-ÏÖé�2ÛŽÌËüíT„°·Ÿavözˆ��ã)qiŽ—¤1y âR±V‡ëSãÎ�>+oé¸ù×ö:1G‹[äEJ¥Ú$<ú<ú@nÖ¼ùðдùŽq»#ü\zO¤¥ÿ£cáÃãäµ8pÀ{O'ÈH¢K»+³uî;íýw\ØoNjWË·#õ%Ù{64¡ÛïO‹ ÒÙÙG/úŸp±©‡ã)q©ç¯äXt²¥ãòœ¾õÁ—=ζ²—úˆ °–¸>5æÜhZž|Û„èÍȸù{X‰|ŸŸtG“æTÎß>k^k˜„yVÿÆ$àËåÎãú™P©y}ˆ&æáÏŽÛ{ßQçß©úþ~K‚}øá‡ÞXx'?22"gÏž•wß}×'ŸH[Sòe»(;ŽÈ¼9qÝÊS},JÑïo$)n¹5ä9dgg¿ºHËs5bÃñTqY“¨�úk»Œ âR±šÆõ©a•$¾ y‡&Cjq‹<Ö’Nj§t’»C‡ycãwíÚ%©TRï\l•m^¯¡rd|u™ÅŸËOôFäŽmNt�îÇS<ⲋÅd> ån#.õ Y\ŸVvMŽº™Ä+ù{H�È?ðÀÞ«ÒVú k}òl•Ǿ¡g°Kr°;zä9Éuû³uîûûȉÍiçe´ç»2oNøý�0á]5bÃñTqiÚÅ’®Ñy³Ð-ƒ�8ұĥ>b¬×§åiß+Í©ÅÅ$^u” »¼~Šy¦ùÌX�.ùX ¯Ýê•¶Êë ö‡öZæßyço›Îd߬۷oÛ¥VX”£½áÙ9CtœP¹‹Qk,Ý^ÿÃÇs1çÍħû{ónÏÎÞæ±Ùt‡]j…ö>žZ§ýãòÙ?ýÑ.­3“€}êéÿcWª=wêªÛeW\C\êkóØ|tß§íR œ‘�Ož²+1vŒÊ¿�ß+nW�‘€¸Ü×ñE»´ÞÚüú´´dÖßâ‰Aé~EoÖÑý²ÌO¶nÒ»ûסGtkZä—W�ÕôÆÆ+m…×$^ÇÉ×{<_l•ýnÉé}vÕÒÙ:ÝM:êÙ+oþžÇÏÅ#6>ާxÄ¥)ý£,,9ž¬Æ!.õ`•¸>¡¾Ö´È㮺qã†|òÉ'²yóf»åδ¶E¾½µ¬E‰ÕÚy$UËZä�{LK[ä‘X­k‘os-l‘ow÷n‹<îª�7®Yh/$ò$‰< B"@‚�È� ÌZ@‚Ð"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰< B"@‚�È� $ò$‰<œTÌ÷HGGG¥dgì;P3Y—ž¼í6%ßÚgLéÉs0IO(&Ä%^øœÃéfF²¡ý¥ªpÎñÍd#±Éš¨¹ªöÜ[SœßoâbÔ#œŠ¹n‡5Z·s­ŽÜL�·ÝêÇ$òpŽ„éá9»fMôS0‚Tÿ„ÝŸW©NKt·™N;~È$déa‰„…¸Dó²'ºó±lâÁI Ócµ×'s&–á´Ë7ããââõ©™º�KuäfãÒŽõcy¸Å$dþA8$Ó¥’”¼2mÖŒ¹aÙãø�Z?6+ؘÀ×·ÛÄCãì3¦LÛMœq¸¥ÌÈŒI¡|,™R3‘2\�K™IÌöèÍŽ!ó"+3V¨ì7A™ÍIʾ¬M<¢ÇUi\úü�8(%¹Ùp,BÅž‡3ƒÎî7ÅüaÑKwÍñdc3qÆÍ3ñL¶?&.ñNÇ�é­ÐTÝΡ:r3qiçú1‰<œ2sÆ;­›1\)ê“q›|ÌMN9Û*ß·Ûœš†¦ÍI{Vr.פc™}$—¾qñëI—eÁÙû?&.ÑÄ+•“‘vºÊµX1¿ÇK̆¦ÇeÀnbórX/Qzvü†FcŠ’÷&#\´d{W$Þ hW͈_Ý““UûFJr'µ¾7'“Sn\¸›©Û¹TGn&.í\?&‘‡C‚û  TˆA‹™1wE Þ6™Ä´4în›Öˆ½£Ÿ{)Tp”íR¯-BZXIqjÒ\‡L%ú%v–†Ì¼æÝ$sý\“ô’¬‰þðøoíV®-ÒŽïOÛ»joˆ¥ºd»y™»âHm¯áº�cuäfê¼m\?&‘‡;Š rY_C'vÌKx •Ë­«hN½‹žkì˜^�üE‹É⇦K2ë| ™­üÔ´!à�U öS\Ÿ$±pE/DÛ¥«�èŽIËjÑ_–ÊÉlÐbZ>¦4‰×îÑ®ö°K˃þÝ�Èz½ò»ÜË峆2êȉD"çdLK0#§?æEïè¤ÀøU4Á—‘±“t��ªnr“ߥžý£^â줣EYðjÑÒ\˜Ê\Ÿ´,­ñÕR2XS…q9éJIÎŽñÒëQå¦Xܤ€£Žœ,$òpŽVË3rzc^üÖC¿5XY0ñÉд«­aщ¨ü‰`ô8s6ñ(w©?ÉþKçœï3•ýÆì8NO:ê±×¥ hW'-«Ek|µÊììÁÄnþü-ŽßÒîÐÁ¤´e:þÛžkâºÝƒ:rÂ�ÈÃv\”'˜¸<æ%h Ù.Ñùb€°J_bÜs¬ÊÄ8—mòÇ:û¢JKPå‘>A ÏÛ3ûMM¥ÛAzmŠœXR¹“þLÛtöÙÖxÚMk¼ž�=ÒÔ7nê7Á¬õ‡~´®&óö†˜_f%'~rmyFuäD"‘‡C옩¸îÐÅ)™ÔŠAæAó) Ž?œ$¸;Š~MÑQ)éòfàšG&Ó^¥ 5ÞñIÜÊ‚+#ƒÑÉZ‚Yë]žÄ7ÆÌk:q[L¼œG9‰Häá�” xƒÈ´»YV*½Íf$›ögäa<+âhïw]$‰_Ip<¹[QJåf#­@~ ºHë>¤ëL2“µ=ÜÝo¼GéõiOu jðly—Ÿ•^Vn�aØŠÇÞ2ûMÍãÔfÎø“º‘|Y:ÁŽûfÿ‰A9‘LepH¡dêÒ%ÝõkŠ©];mz(>.AÉŒ™è¹É$`ñ1 Š«±YfŸÉŒ¹º·ÔìG®Ÿj–ÛoÜŽÍ2×'*¹¾ÛTâ“)qz YáÚíì1U+™´´6&®]¯›ªÛ9TGn&.MÅp}Ñ"ÇøsE‡bzÄÐÌ ¬}ä­Íh’“éöi8þúd*ÐR*�3œÖøxÞ8p;�[ÿ\LÕ&àÇ£4K«r}Ô‘“¦C³y» Ú-ò$‰< Ò¢D¾(ÒÓ!ÒaJeZÄh—ß@ch‘ AZ4Ù�¶„§õQ…"ÓæŸoÙDˆíò;Üsûöm»„¨¥ÛÌ?‰ælº¿Ã.�ûìŸþh—Ü©�îû´]s_Çíª,-ÙDÝŸª~æB�ù¿»yUéñòÞżÿ~�y­t]ýmV×mò¬úƒï7%o?|gÖöwÏ›¿>Þv7~m¦6‘ŸÉšd¶ß®„™Œ7½ÞI€{ ™0¿/.™àIäMrÜ?á/M‹h¯û ŒeüíÃ{*­Ú«1®ßW±_çuiþ�\uw™ö[ÌÇÌçƒÏLÙ÷L2'¹|3¿€6Q�Èçû¯šÄ�GŒçfÍv]0™õkëÕn²ìB$±î·¿Ã8ÓúVù7nÈ~ðƒšr§Þyçï{ôu-Ý­ß X¡D¾(2i»°ï®3ëÛn›A_^ð_ï¶Ì H\ãøzÿŽe|òÉ'ràÀ�šr§¼ïÑ×µt·~o-í•ÎÎÎJÉ�³ïà½Ø éLyâ¨\³Ûà#6ñ8žâ—z®É?l“Omª”¯MpD—固~wBú7l•�¡Ò”Ø—åq}ZÙâ‰AéL§½’{Ïnl#1“ÝeDÒv1*ý ]h±vùÆæÍ›E'þ×òî»ïÚ­í+i¿÷n9—ë”ìšõÖ“ÒÙ{Tíª‹‚$õÙÛ (#6õq<Å#.u\ü'“„=*ßùÀ®[ï�>*Ÿ:ðK»æ âR±YÆ/ä@÷!‰„F>øî.Ù˜û…]sqY×§\;!ÙWæíJ{ŠIä±^®_¿n—£ÝâÑè皥ß{óæM»–`çròä[º°ONߺ%·¼rÚ¬—Jö¨›§0MTý$u‡¼<{Zžò¶B›ep<Å#.õíê•ç¤[þ篮Ê,Ùrê¯ý÷~tA~æ/¹‡¸ÔGl–·cTþíæ¢ÜÊü¨<¤ÛO\w›l â�ëS®ÉÑ—^‘K¦Æwäån»­ýÄ$òs"»U¸bZ¬]~G“4 >~ü¸tttxeË–-Þë /¼°lú_ýêWòÄOȦM›¼Ï×7ÿöÛo{ïŸmúfAT0¦>øÞÏ}îsÞ²þw$Õ¹)={í�#óyyÔßd<*ùù#f«9‡ýäçNÞ�ÜùØ^‘¿9-…ߟ—½f7ÂClêãxŠG\–óWrlémù»?·«jד©gtaAþÝÛâ âR±©ïëòúù½�|i¯¼h.[n#.õp}ZÙ≗äà¼È¾7Écv[; %ò)‘ív±Þ$rgìŒöƒþkØ\Lr]œòg�_­¹Éøò×ûw¬M䇆†äù矗×_Ý+º|ìØ1Ù¶m[lÒ­ã׿úÕ¯V%ïšÔ_¼xÑ®ù4iúé§Ëß­¯‡ön¬¶…^ï³Ï>ëý†ðoÖ¢wï’éœøç¯oÈc[ý-¾E9š=(^'£KWÝ<�íÊKá�>=lW¢¸ià1Ù·>»>ü‘|�ý‘�õúkcÆ òñÇËo¼!/¾ø¢WtùñÇ÷Þ/ãŸë722R×®I´³ÿ¯ m±×¤]“íà»õ5øìOúSïµY¿ýíoË7¾ÿýï—sPi±(¿Ñ×/§$8é8¡ÎÎn© ú�©a+ãxŠG\Vá—ræGæå¡>¨jBsq©�ØT\“ñGB“º=yJ¾}zQ¦÷o±ï»Š¸Ôàú´Û¥¾ûeßÛþûIu"ŸÊUž¯É|‡I¢ƒbÁezÜ.úâÿÆ{½IúƒGÅE3χÿ&Id3ö{ûíûZ†mÓúØI/¯¸‹¿c�h"¯%jçNÿvÏâbíQ£Éù¡C‡ìšHoo¯÷ª vÐÒ>55å½~ó›ßô^ÝÝþ˜íp§ôfÀÝ{ß ;¶m-ÏÖé�2ÛŽÌËüíT OñˆKã~và99®c ÿמråÄe9ÄfyÿûIfh�C\|\Ÿâù]ê»åÈk{q^©#¯Ï‹/TZ{Ë2f[©äåË5ôo¦#É¢>‹¾MúCÂσ÷DZØ=ƒþ¿ÍC§#Ï–ܵ߱vt,|xœ¼–àñoqÝÕS©êßòùÏÞ.ù�’SÚ¯~øáªïÕõ;ñ•¯|Åë  ôf€Ž‘×ñüz!é“Þ]:Ø]™­sßi/öö›“ÚÕòíH âxŠG\ó³ÛdàG"Ï�ŠŒ�vq©�ØDm‘ìùФn7(ß6[u†öçýO¸‰¸ÔÃõ)F¹Kýk’”N1“ÝÚ2¯ t¸ÌšmËÑ„8üùq›ñ�ëºI°ãrcï½ ÔùŒªúœ)usï¹›¿ã}øá‡ÞXxMŠ5A>{ö¬÷8mu_ Ñqìá²ZÚàý÷ß/'ô:ž_Çè?óÌ3ÉLæ·¦äËvQv‘ys⺕¦úX”¢ßßHRÜÞVÆñ�¸4¬’�]•c»ìF—e›F|]^·3´_)Ðú\A\¸>Õ·ø¯ÓÞ—^é-?;^K·}Ý[Ïúë½'Úg߉OäqWh¬4±ÖYÇÆïÚµ«¦Õ}9øÃì’ßU_cìûûûkƱåNh˼þ^ßêÔ)o›¶Êÿú׿ö–“e«lóz í�#ãû«»Í,þ\~¢GðŽmtÓÂñ�¸¬ìšüó Y-âR±î×§{ ‰ü: ùxÀ{UÚJt­_‰¶€ŸrbPÇS<â²�¿�ÿúŸ]Ÿð.Œ¸p}ªoëÞI¹U(Ô”yûù}oúëÚh¼Ž’N…ÞnŠy¦y—¿R—þÓDüÂ… Þ²Î:nuW]]]åsM´ƒ‰çô}máÖñíú¾¶pë¶ õ\ŸÝ$øÁw½÷Þ{Þçôóo¾ù¦lܸÑÛ® ·vu×÷Tðy¥3<<ìµü«f~¯>âNgÇ×Éø‚›ú}Áßèï�›¼¯·oß¶K­°(G{ós†è8¡r£ÖXºÝ¢CòbNÒÏœ°+1þòU¹pv¿8yékóØlº¿Ã.µB{O­ÓþqùìŸþh—Ö×âÄSÒ5êwUŒõÐ?Ê”{˜—ú’›�îû´]ZgçG¼ÙØã<ôêEwghO@\îëø¢]Zom~}ZZ² íañÄ ×½^ùV?ŽîþH/îöl‘Æè·y¯>úè#/áÖ$¸*ئ“ÛËcÍõ=Mâuœ¼&ÚQ:ã|0v>ø® Ù'ñJ“iÝ$ãÁ烿ù¾àmWÍüÞÏ|æ3Þßëv}F½ýýo¸víÚª“øÖÛ*û/Ü’Óû쪥³uº›t«Åñ�¸h•§åÔM?W‹¸ø¸>Ý+Ú³Eþ§ÝßuÆùÍ›7Û-Ë»~ýº÷Úìç5Ñ^‹d;ø½ªÑß°’Ö¶È··–µÈ#±ZÛ"�¤jU‹

Ù úììwÂ?vfa}Èü¢¿|ɽÝÂ=öñ.í6éü"Ã-]!]PF4)* Œh¿˜7‹ÑЉnƒnŠœܽÃÕÂ0ÆkPF4)* Œh¤Åãšz6È�2b^ž6%þ¸ûË=„�°oÆàÔ „Ósû§3Y2ÆÕ*#–˧çßÍf�Þ�ÖgEŠ.� QF,—³g[͵Ûñh}V¤èLql•‹æý˦þÇoGë³¢Œ˜‘ÓÆÓOåÛ—°Ów ïœô¾kø[¾iXº¾á\Ôç¦ÝqzŠj•Ë%{ëÕ§Ñú¬HÑ 4ʈå…ÝM¸ EŠ.ÀÇVé°h¢°» w¡(#–ÈF{Ùï°;k='…Û¶âzeK×ç.¦»31-ëëk;LR­2b©üö*úf´>3Rt>tÂeÄr9¾ßœåfgØ“[¥Ã¢yû°9¼vgØ]6DØý5azÝß%úxð ZÜ~!èøäy·ÁíÓ ;IZ¯½¢�®Ï'\<§“³»¬§¨Ñaz’j•sÓ{îÌôCgtÂeÄR‰OŸm?;Õë3#Eç3ɱU:,˜øôÙÝ—gz}f”KdSn‰§�{¥ ·oH»¤4ÞK‹}ÿ-6í{ (CO.ìp&¦ ètŠv«dÁ“T«ŒX.ïÂ@möwØtÂeÄ" OŸJ|’c«tX*áé³F‚.ž÷'ì›26º{n[Øñ"zxñ¼¸À¶ÇÕ*#–L˜mo?ÿ8ZŸ)ºt‚F1?ÉÖÅo†§HјâØ*I²uñ›á)ʈ%‚°ýqëÂ×ÎnFéIªUF,™ ì¿Ɩ¢ @'h”s?ÍUÉÖM¤èLql• $~š«’­›(#–ÂöÇ­ ;��Ýé9X°Hýj•‹åãëðh°Ý§Äé�2bVN÷î4V³u)ºõ�­ÒanÎ^4î«gë&ʈ%‚°ýa@Ø ñ”L˜�R-¨\­2bNÞ<’:#…o†§HÑ¥ z(#f$~ŽkDÙïN‘¢KQùØ*æ%~ŽkDÙïNQFÌÉ——Kº!~2»XÄ-·$lh‘®‡ (#� PF4)Ú J‡ö£Œh<≄}ËH×C”�GІ (#�í¥CûQF4ñD ¾e¤ë¡ʈÆ#EC”�GŠv‚Ò¡ý(#�x¢aß2ÒõPeDã‘¢¡ʈÆ#E;AéÐ~”�G<Ñ‚°oéz¨€2¢ñHÑPeDã‘¢� th?ʈÆ#žhAØ@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø@Ø›ÎÉάc¾.k­Bµé—1]³§jGµFvNäecøª62(Ùt¥™ ì�æ|¾çbÓ[nvª­K(ÒÏ`ç«ZE(ÞôåÐãÕË K~ã:6tÅøiuª-�/úªv­[ÝC¯:â™é¦Õ©¶4{|Å|µª@_WC7aCGè|?­Nµ¥‰5vXWНjp¡YšX£œ]±+ü4Ã�AØ øjuª­K½\9ª6–êæ:ÓMµ"íµ–uaC õ»—f§Ú p3VG¼T.Ýü´‚�jÓ øéŸ~ÚöÆ lpÖåT; ±r7nñQm¬ÒM/ø¨vTe¼�åð„û&öÆûÛMƒSíTø˜]µ¸¨6¶ƒ›npRíè'ïëªè† ìÍ&6·›î¦Ú©àH—'VÉ-‹âÄ ‹E¥¾®ŠnJUaŸ\Ìfå]¥·Ý@Ò@§±zh©¶.ƒŠÍÕ¾ªm`z]�Xm‡›²Wa8àjaŸ_ÌgÁŽ’Y�ØŸ_ÌæÍ& NvÂfɤáÕþ¾í–a›ø>;½Wûò]ímÖš+„'²ó}YjŠí òj³–M…Ó6ýõÉÁÝV«½-ÀZs¹°ƒž‡SêdÊNá9ÂüÊ©ÄÛ¬5— û)ö-¾º°‡{5ä¿-ÀZƒ°ýñåŸ IùýóWR)ý“TÊ}vy €°ý¡†l²ÉQŽ!£C FÑxdä5À͇ݙR{4¹ö[„½ìwØ™o»I¨!›lr”cHÁ(Ç�‚QF4y p¹°Ï÷‡^Lñê+<Šsð°X³A߬q—þsæ ÉÇM:g‡+ƒþ”z¥·Ý$Ô�M69Ê1¤`”cHÁ(#�Œ¼¸\Ø�èÎ.cG&©§4jO‹ý©ïbƒVÉAØÍ¿ûŸðÝ!_ám7 5dçæãë-ù† ÙìÑ;ýªÁ= ¥~÷ú7µÞdÞ<Š6²õê“z5?Ê1¹9Ý»#ÅÎfŽõ«sð8”ºµw¤Ö—ˆrLnÎ^Ü‹Ç5ðø­~Õ`Ž¥n¿ø Ö—ˆ2bnþØý>×À�úÕìÈÈk€«…]¶,Á ¨!;+ÑÖŽÒâ»M»›vvªðõóï|û·WÛ‹:ãuF{¨‹E9&+ÑÖ÷ÒâñýP®eg§ ÷ž5l_ØÑÖ�ÓâÛ‡Må¦��*|ñrÛƒ°£­Ÿ¦Åƒ§Må¥�-#¯¶?Ô��‘OÁ|}C©l?ÿØm`*¡Ú(¼X¶“v/££]"Ê19 æë:L^·Ÿ�v˜J¨6^[IJ­ û}0_ßÐaòz÷åY·�©„jãµE,Ûº°Oi.…ú†>l®ê·~ù£Û @dä5ÀF {øUñý?�þ¢@øÅ}÷·º/ݫݩ÷çúŒ@ï× v¾õ9:5dg$LXûs¾tÿ¶Æ�Û¢ñ,ìÒe+Çd$LXÛéuÈÏánÆìÎîY·Æd\;LXÛéuÈa¼‘oï}·Æd\;L©Ûéuȯ?†cûýËÓnM~dä5ÂNˆB“@çûûý-äµK÷X·eð†šôâ’7ü&g«!{õ„ûáÝ|:èdëÕ»³ÀÒq*ì*¿qPŽY=á~x7Ÿ¼³{¬çÜãAØá~x7Ÿ¼·÷VϹ-ƃ°Ãýðn>fÛß¿<ÐsîìÈÈk€É…mQjR¥{>ON¼6 ÿJ'å�¿{/uû'E÷ÿ+ýšxþJÔ�½z7À»©v•Û¶¥ãRØñîEù_7(ǬžÅ ðnª]ˆ°ó³¸ÞMµ£ v~7À»©öè&yvdä5À† {<¹Xôo^²—Rï`ñÒYtû?iÚw¹5d¯ž4Ã>ϳ¹%^8áé³:¿kPŽY=i†}0žgsK<;i†}<žgsK<;i†}8žgsK|MX®Ê�F—øúн†Ö‹ÊØÝ;/¿i…�nÒΞ¼®AØe“l]é¾…rLFâC׳Ç?× ì‰]Ï~8®AØ% Ÿ=øu¸a¯ I¡�~UîíqÅ^j§þbÜëÊéú’ÿê:Ô��‘Ñ pÓO‰wq%ìô©ñj¿ePŽÉÈè¸é§Ä»¸öè¸é§Ä»¸6O‰¯9"TEçå¾b¯ë„­hý½ì—»]¡†ì¬ >\婨 ñ#ìô¥45©rLVâ—�øùvŠ aË—�øùvŠ a'Có9ì5¦¯ÊùþI\ºNØWì¥v..Söâýõ«“ »IšFŒß —{ËCŠI¹Ä ‹e VŽÉMúâ°ˆñ›áG»Kš¡ÿ±´ü(Çä&}qXÄøÍð{w¥Ðý�¥åG17ÑÙ‰²7ÃSdä5À†>t6ÉÇ} �×Ü5d“MŽr )åR0ʈÆ##¯vM–N¯/™À;jÈ&›åR0Ê1¤`”�GF^ ìʬtÓûjÔ�M69Ê1¤`”cHÁ(#�Œ¼@ØþPC6Ùä(Ç�‚QŽ!£Œh<2òa8a8a8a˜çââÿ—Kë.OóiIEND®B`‚numpy-groupies-0.11.3/diagrams/multi_cumsum.png000066400000000000000000000203511501360642500216220ustar00rootroot00000000000000‰PNG  IHDRzƒTDþcsRGB®ÎégAMA±� üa pHYsÃÃÇo¨d ~IDATx^íÝ_lÇ�ðÓ—�ã¢@œ‡<øI©U ¬'9Õ¶ìˆ*r†«ÊÐT“9¥0œÿ[*„“ÿI& Dˆh’êNT]âËETlÙNTlªÄQ°HÔë¡Hê=ØIx3»³äRlÉâ�3Ò÷ƒ­3³ä®ÆêîúË�™¥#‘H€ˆÿH€»›–;w(¢ xyÕ*jm5ÖÀ‚ nZŽ£«Wiß>^fq“-°`èL·Ü¹C6PI _�52dÑÅÍp19UÈ©E×™Îâf0F¡8\bÍ56¦þ„{™œœ%|õ�C” Ó·%È´G—ḕåNâkQ‚%ïAÇ#¢¤ƒ‡n)J:x`Ež(Írw³›ß ôw‹š^½Äô|³&È�±› Ñ´¸ÉÇ>zy!âå÷8Å4l®4 I~ö’_”¹a*vPqXÔÌ*ßÜ\ìÁ¶‰ycu8œÚÔ;­ÝþÔKS^5_J®áUã§ó›Kú_dš7ygt”|QÞ+�è%Qy%QŒº’1­›"ìÏH*Õ w±×©²Ü¨t“ÃM267_ä^!�½ÁMõÆ›£>žt‹‹É=`mNäµmÎâéáñÒôWgÀvî ‚¸ñþ8ÅÈ}×ø ™3·ÎtW9ut‰j÷ybu¶æ¼•7ã|M¹1bÒïeY•zÆ †–_cT�¼÷9“h‚ÊŒBÙ¾çØZJ´u¢=¡´°Ë±}çS^�Û¹Íé¢z–OmAy1éö;¸»�ºßûMÙ6Ò¼ùaKà¢X©²K»ò~²,ok󸨫MŸÖ^Ý·Üý˜X*O�еªÒ«µš�eÚ]zêW,w®(muµéÕZF£ktè^ùÃ�Í·/OF$þ+1DZ›.ª,¢Ë”†¡ëä«çk®‰5ç#TTÉ7>}ÛÌÕ–2~2¹ù ŠÈ-JBQ�(0®Ô̦û’¾s·±ç¡»ÆSÈ&vrzö?Þù­¡³¶µBíS´§�E·®ŸzBÔÕ¦SkÇO—¾ðÅÑKÿ˜ˆ³ålM£§áŠxIAzµV³³L³kÂG»YzßÕT(êjÓ«µŒV ÖìÐevtýsð{kù‹o¥X-Áœ§ ñþtó¦à0uĨÀÍ×Ä:Œ^l#bŠžô¬àÃ=­±˜î X e- ®Å¼M¬‰‘fÿþ¾ÚÎð³º%ÜYK­'šG̪jFÛ·n§ŽÉ¡“O‹JÓ«µ´rgOü½:qµÛ8´ŽÞºÐcÖ¤Ukõ:ËôjíxKéóÔ9r+üK±Bizµ–Ñ«ÁzºÙ6ç¸iö§óÞó8ÅŒ~s¾&Æj4<”êIÏ¿ƒXÂŒ[c7ã!±^&ÑMmWÌ%oöLóŽiÑG-Ö›’ë Éîë)ÝÙöªµ?Û–¶ýÍj–d“ú ¸ƒ1±ÎZ™ú ÓVdÁÈ`Õ–‹³“¹ØÕJÔ÷Î5OÐ5Õ&›6‹ŠòôjíL¾ˆë1\Á¤nkõ:ËôjíJÏÈk¥¢¢<½ZËèÕ`½Ýl›sÜ4ûÓ#çùÀMÑoFíê°Ö0F¿9{[š{Øï�¹«zëÇe s^>5jVׇÂþdŠ‹«�´Æ¢›-Ù1ï\s\ÄkÛ2tÏ”ç�‡Èÿe-‰(å�l1WQ(Þ›ÅÇ–Ž ß Â|§¨4o~øD~ç±BêÄù¹Ä�Œ\#ú™[b×N&©ÝZ½Î2\@SZºo—geà&3cÜtó™Éq™I¼?ý:�¿Nk­Á”Û|9ÌûÖí=é|îNÄö”øa*6&e¦�×h[*ÎvËïLîbAòE�Žê„ÐxÕžÉ::",¥%ñkÅâ,¢6žµ™x%øê“£ØÎüa‘䔬Y¸½©2ÅÌ-±”µX�3,Âf{v‘Ï? š.ö îýöR�8Ya)»º¯â-*<ôïzÜÖP½µz�e¸&€¦´;t7þgrÔæ÷Ÿ¤†-?Úý±xI‚㦋ÚC,‚L}J¥Ù{‰Ñ6+8–mcQEô­'¹üQJâ±�l1ŠdM3_(õÆ©Èx¶_SœUÅkr¸òÖ²?#^£ŸYÜ'ô%\,FfJ±�ëø¿=YÚ#Þç ðñ<ÄÏåþ¦/*²y ôÌܶšÙÔ–LUÖÂh,d¯ô�Î|>þÛüØÿ­5ä%õ – > çOäil©Y%Ö¨LƒÖêu–áššÒúÐ]]ÝÞ䡳—>õÌ›¥3�GFsp¤ñÜM�E=ce*¸”M{�É\o-öçMÇŸô9í§¤mbì-•ƒ¬fð…mhT“¯–µðõÉòjzóÌ¿Ú|R•È|IE¡ø”;µE•Y¹*ÕBbí}rº§¾ýöÏ�©Oˆ°±ô¶¹±�~ÛÙ±s�X¥0=Z«×Y†khj1ºCCÒž55÷±›K–1¤Ñè+¦ß\›7u�» ½ó\tiOϥݩQŸÓ™£)–fEK:Ì'“‡«§ü8£ì¯gÅ™MÆçÁ©ƒ« ·oÕáã dÜ•€™ÞâÇuèFצµz�e¸&€¦ô>tãCý´¾¬|µ¨fâæÜÄ‚nÑ™n¸çœ«óÜêƒ73¥¯ž'U³w^¼däÉ©¬­Ìͭ.‚«ØZ´ÑU^i&_ã/0%ÛŠYPü'X£8:1i¾œu{k©µÂz6ÙÅ@E+ÕîU|Ä Hq³¹ò¹6m²¦N­Õë,Ã54¥ñ¡;y¶ü,íü�jy�u$ Q\šÎŸ§¿ÿ�.\ ;(8ÓÄ£nÿ ©ÐM´”±¸ÆœQëmÒ¶K›ñ�z…­n§j¶ûþRÒ¶šÍ´­ ¶Mm ñEã‡Í·gÈÜ,Õzë}³ü}l&''E)#.f§¥¡ðXƇWõ�C”lü�M¥§öŽìèPôyCYh­ãö¢´P£mÏxŽ\•¤gÏN4me•d£µ�.ËØqËI>Ë2Lrkï$¾¥kþ͆Ÿ‹Šåw�Š>¾G¯Ö2YhðƒŽGD)#$ºÝþR”ŒE̼†~QYp¨+óYó�©¯éYòqóå—EÜd¾û.•”kmÌf�}Ó×dÌÝã«B27%Ë`Ü„)27aª ÇM°É`ÜÝe8nJ–Á¸™ö¸¹ä;ÓOž¤?äK @}$VNgïL7oÞ«‡;£XÂ�U–;¿æc7ïE< Ý®(Wýî#€"–|gzRccêO¸t¦ƒ �éò 3]t¦C:ÓåAg:d â&H„¸ - n†‹ù·–gý[h@#¸» ¹v1ð°�õ} ªº´+ï'ËìËŽ7FÅKjJkð®�ÅZU] ¸[n.•§ÕþÅšÌ?Ó<.ê겟g››GÄZ5¥_ÕÜS¿b¹ÓZê/‹µªJk-_¼¿Éf+ÿëµ½ª·DìÎÿÑ£by2"÷0X@Ü ôR"AÙ|ú$uóû©xÎä"³%ümRgmk…ò‰“jÏ|39d-o¿¸F¬VËš•7]M=SÝú‚‰süt©û9:ó�‰8[z�R£GíÄÙÓÀ‚æ…üCëD]e#Í›+në·Î²¾ýõϲN³µ¥¿‰Å ŠÁ¦þ‘[|9Ws®JýHDUçŒÖšK´n¥X­®ñ–çDQaìD{¸‚ġیö{TNœü‹+¿88ôÏÁïÙrnÇ_¶¼xE¼$în‚J¶ì:VH­]ªÿK¨‹�»Z©zõjQ}ª¼–èÆØ˜¨*¦çLcß³gÃÿjÖVÕ½ÚXØß¾jV•3ÚöL��ˆZ¬P›³îR*²á,˨Ëš—üÖ§ÎM¿ª"ÖáÖ¼FÆšw7ôU�ëd¿[•]<µ¿¯¶3¼Å¬9ëZŽöí?¥ì™æô½õ}ò‹+7>r=�ŽJ¼±€¸Ùíç÷“·yÕÏ ~_Ï—b²ìœá ¶Í>tú&Ö{ø«^^ˆxÓw>LÅÉû‡y™ú�EÅ9Ž[˜¯ð� í Ä“$ãÃ7ˆ_m¥OµÜŒŠ’°ÒYÀZ?vST³¦æ=5¿Ì}În «Þϧ‘�›Éqcü@Îw)Üç¡¡�^?ðùú£;7‰ªªFøÖÎéz\³3í‹1y�ÍìÝÍÏ|qÞÉžˆSQŒÜSòŸñŠoHP¨ˆ¼é‰ó.xß}”|ææ½dvãûÝD!±Ãx‡�µpßbAwÎçH³Ÿ}8Ü«rÇ×ú‚&7ŸÚuÜóé¾R³ýÒ®Òƒ}žW‚O™¯©oM¾G” ³FûØç—Ú§Yk…ÿ¦ú¸M�†õ}MÌôžú Z#:W¥ÏÀÍË�çÿXØðº=þÓ8ó EI}ãŸýlµ¼ËB¦;Ó£ÉÑœ.ª÷ñ|9%M†â”üþÇ@=ÿóðBb7û ´Özl½+@½Q†9*kI¤ÄÙG–8âækÙb�­öØ{"Ô´ùTrÔæÐµãtðçy/÷ˆ—´ºîíoþïÐF>6qª;ÒtÕÓÞuôV(ùOßhÛ®ý¢ ™t1PÑJ…Çv)|žÙÇsà”н¦&:q¥©ÐpÆ NÕ‡B–NŽÚ¼Õß@6®|$^RÐhÛµÑïöÖ¨Ù+“ƹu{!µžH~<2²ê>ؽó4yN¾$ñvDfãf¹E‰sð?‡ì7ÊŠ¨Ü>µ¨ŒX"� ˆÚý0öñbþÐBغÓÝÁ˜X™U©]:©BýÙé–Õu'^)¤öÿUwº÷XóŽŸüüHÁ;,÷¼BGÖ-S8¯©y¯ÿØ,f¦û)xw73ŽÏâa³Eõ.„$cœÒCMGÛ¼Ë76¼ÉÒÛߎRƒÇ©tz›bMÍëGAm—T�Û4Þâoú¬æÍ×JE]mκKæÇ#“Ÿö²cW|ÎÐYú—¦ã»d†zý§ µ�òb@'ÆnÎ×p¸Øá�ˆŠ¶„;kíu ìä›Ñö÷õW¿3t’_ªW¾xy¨£–Ú·7\2_Uɧ¥ó¥§†û©`õ*ñ,Ëšžý}TÛ©ôDï™);Nä¡[áVY]÷ç[�UÔöün…ûjpèº(Ì…+ÀosÒõ´¹ípîž/#ÞÜv¦']}ªŸŸ–ñ76½ÐN;ö(;P«´´šúîl·n¾~|j_?Õ–nU…õ4<æ9BG_݉ù½bv¢ë˜5͆«;°¤¬†>;°»Åº;x9ÜôU•éÑùKcÍ¿©Rxdä¦pêá |áB2ŸzXùÉX{ö“ÚÃVÆÚŸ¬z;;Y“ÉúÝÍ ;õ½—~/͹ǚ9TfäÎäÌ¡p1MÍ>növº>$jÌpØv;s˜:Øû׊ë÷abB–W ÝvC³(ÍúíÍ´/ñ¼³½_éG:³ˆ™ü’žÒwÝóÍd“Âéí©““gªûެ ~¡½öÌ7§”�™Îó.n�´|Oé Vk=G®]CNd>$y¬) ©ÙÞœ²c¤YÄM—¥C2‹DçjRwàªÚXR: ±ˆ)n.wnx·üo#šŒŒÔ€íÐ5žö®ô°•ñS¿å¯ì¿gw&¿Xˆ-òžôîH$¢8_Ý~òFøTt3.òêuŠ[Ï'bXtùTts®ºxC;U»IÄÈ¢´÷3æ&&_”ö ñjòG0©7XÛ¦¥R%ZDq¾nÞ¤ª*úä^.)¡?4ÖÂÌ&''EI_}ç%È4ÇíD 2íÑe8ne¹“øZ”`É{Ðñˆ(éà¡Û_Š’Xa=8hAqs¾¦çQÐâ&˜7åAÜ”q’7å±ÇÍE1UT…¸ ¡3$ÊbÜ€¥�é â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› â&H„¸ 9‰„(.qwîP(D.ðòªUÔÚj¬€AÜ´;FW¯Ò¾}¼Ìâ&[`ÁЙn¹s‡6l ’¾ kdâ&H„¸ !n€Dˆ› â&H„¸ !n€Dˆ› €áp±Ãâï+UÖíçM-‹ºÚôi­ÙR“úíÕ«µš�e¸&H¥Wk�¬Ñ¡k¿„¤þ‚7!רÉé®�& Q_Ä«ö)jœ ç BE¢®6�ZËïõPÜ:bA·Ê‚^­Õì,Ã5A"½ZËhÕ`Í]Æ'kè ¸Äj 7!·†ÃÕÁ˜/ÚRfVËZ¢>ŠVõ3,ÏÄÎΖr±Bizµ–\�ÞÔÕ®lû×%r^Ý+µV­Õí,Ã5AÍ® úýzu:t³M©¸ÙMeþ£€¤ÝBFÄbäÛ&ÎN¦û|„(ÖÑ¥æ Ê3†u)QŸ^­�Éõ!�.Ôê¶V¯³ ×y´»&èÕ`½ÝlÃÝMÈ©á¡ëTTà•p±ãpA4TD±�¸¹ –*~ᦵy»v2IíÖêu–áššÒòÐ�x‚쑱™�›ÃTìà·Åâ«Mábr³·¤tûùÛÌÛŽüU//D¼Æ¶Ö;ù{Œýøm{¶ß©¼¿Ý‚"Œ¦ Ý~‡{ >Ñ'+,eÝ~o„ŠB{ô¸­¡zkõ:ËpMMiwè–µˆ1›L\‹Ç·v™ÃÖ3FƒÖêu–áššÒúÐuÚY¸’9=sqÓïe™Žz¢Ê´°œ£ê°¨.D(NÉÿëõüÏÙØ-äœ+o-Å‚^ãs ø×:õ – –ÞÜÁû|(uŽd¦èÑZ½Î2\@S‹áЕ8=Sq³›",mn5¡ŒåOŠ ˆÚý+¢rû¥…<5‹~˜Š�ÉCÉ�WfŒìLNñ1Ca7±¨}}HÔì‚nJ>ŠÏI*¢ä3G²[�,sqÓà��·d‹ùP$ûÍÎ2Ї(o¨çÕ4.jO¾Áþ¥"ŠÇ©Ã-6Œ°ªýyF÷½[�.s�é’ð¾òëéùRt¦H€¸iAÜœ�ÉÉIQÒÁWß9D 2ÍqûQ‚L{tŽ[Yî$¾%Xòt<"J:xèö—¢¤ƒV䉄R!n€DÊÇͲ|Ñ9€¾pw²àbàafs󈨛kEe÷·•d—våýd™}ÙñƨxI)f;·6�‹º!½ñ —Äj…\ ¸[n.•§•üÅ2f#ŸIÿÝ27›+­Æ»÷õˆ•Ù£×Y¦Wk不~År§µÔ_kÕc¶³´mLÔ i�wîÎþ‘;›ZûÑn[S“KvÛ¼¨ÝvçÿèQ±<™zIË,ÄMÈ’ÂBzçBêT›£ûÛJ²Ú3ßLYËÛ/®«UÑÓÀÒd×O=!ê)›O%›=ÔQûv¥Z‰süt©û9:ó�‰8[z�R£G½ÄÙÓÀ¢ä…üCëD=…eÍâùg�Æÿ£óÙ?Uä"qjv–-’kKEƒMý#·&ør®æ\•ЉÓjﻚ E=¥ô°Ùr¾tVý±B…Ä9[kK^K6Õj0Ñ/ÜNñrÖ,ŽCw$òlù‡þ9ø=[ÎíøkÖ¯ˆ—d@Ü„lÙ¾ýñý§æýYîþ¶ZºFÛ·n§ŽÉ¡“O‹³Ùdyôí.…neœiì{ölø_ÍÚªºW ûÃWͪFÛž© (�OÿÝŽ¶í:ÐÿÛΦ�fµ´él ýéDÛM³šEz�e‹âšpùýsTó’ßúÔ¹éW, «õAi¼¥ôyê¹þ¥X1›Ò�,áýñý�D57æÜZöÎi¿üìY‡®Ó÷Ö÷]Õ"«o|þäz:ýجɀ¸™CÃábG’õ}LbåôªQ*‡ý¼Î Ýbëbëû–ÒvLj]Üÿ3mkymk׌§šÙÏ Lé^˜}+˜Ášê “M›Eåž<ùY¿-0››ñAQV: ˆƲŸØfµ¦æ½ +PN12r�žÝZ*jDW/´õE{²:ô:ËË5aàf²ÃwŒÈù.µúµ(åÆœ[Ûsº¡ïM�QͪÅrèNñŘ¼»¯ˆ›¹Òíw¸í_·ñZ�ðnbÁ`D¼bëXG‹‡,;¦ín®fÝ¡ [v»qÂ6vÅ4Ò¼¹«ü[¡ÿXak…}|Ël[åRë ÖðGEnÎÁø;�|Zû’r#lÖä{DIy<+:Åïr´í™å¯»:­£þá¶z�e‹àš°)а¾¯iƒÙ�ÞS¿áÀçë�îÜd¾¦›±æÝ }UÿQ·RÔ•fÜÚ,,ß’£kØ"8tÓ�|Fô³Õòî@ næF·ßk¤<_4!D}Æ ÷Ķˆ‡Šì¥Ø@œ(>À³bQ(.vÇ$¿­þîfÙ¡έۧ�]qÖ] oeþŽBêL{ËŒ[åŠ}øãµãtðçy/«Ó=Öl¡Òƒ};:N=%Ö*`ÕÓÞuôV(9ÿÆèžeå�ZMíixÌ3œèØ™»ÛÆz�ezµvFkj¢Wš ÏU-wšƒ8£zÄ5k¶ËÊ¿ë<¬GVýðúèw{krv'vº6ìÞyš<'_’øoâfN ]çÿñES‘°¬eNñзÍzWªÄ•mãq5týáÉ®ó{›m‡rðSmúØ{çƒgŸX›2óV¹·ºîÄ+…Ôþ¿G»d\*.¿C•ËTÊÊkjÞë?D6‹ÉÝ~ Õæî¦y#v´¹ò±ÎÞT‡»Ç•“Щ×Y¦ÿ5a´Í»|cCÁ›·&Fþv”<Î�ÜŽ}œ¿Ôl¡7©B�ö_7}VØðÒœGH ÿ¡kás†ÎÒ¿4ß%3¼#n.e-‰„u›’wÍÏ+sf�³nï”±+ìä¬h-<ÖŸê~/ØLßJ%7ÆÒ+¢‹Ò¦ŽZj?Ö®PãùàHcf7[zjh°Ÿ V¯¯)m•;ŸúŽ<7øk¶h0Í™+z�eš_Æ[üMŸÕ¼y+ÌG®®û3Ÿ+Ýö¼B�šŸ’×xû_oQ|”ÐhÛµåôÖ¦AóCW`Y3¯¡Ÿž;ýŸÜ»òˆ›9áÊ[Ëÿc¯ÙíŸ!vûç7 ÓèåÝè"u^š6s¾;”`KúXé‘áDµ{ëîqhÊVªì£'~]’ÛkÞ‚<¾ZÑÆ�¶…Ú<�1Q]uNç:¢ßþ*ÕÚ+ï¿E…ÞÒ\ �Õë,Óúš0ïKŸ[ãt­%}©6Õi*~k“ªÊrykÓ¤õ¡k¸ò3k~ÿšô¡Uˆ›6¢ _Ù3w! æXN[µ¯¼·ns~¹IDʵyüÛ˜îs‡òð±Ò]]¢Â®Í�ÝS*Fš7ÏÔûÀ¥o¥„ñ76½ÐN;öh7PË0Ö¼£²ÕóJP¡á›)|ä:úêNµÿÑKYS¬¡?U4ˆ§Öõ4<×F¿ÝkÝé̽Î2�¯ %e5ôÙ�ÝÉÛ�Ê$¡û2Úæ­8§úT'ãÖ¦"�ÔùÐeÿ ´?Yõvv²&ƒ¸i©©¡O>!‡ƒ/¿¼ç㾌߇L›”ÇYÖ’Z_Š'ûÇçɵæ eh‡™ãܺýFk«¨Ð–pgmß~�9Òå�íý�µâ…)Ò·Ê1Í©6|¶Í»¿îùfOÊ«…??ò)ѧûJ�¦š�sÿøeÑr¾¬ûoï5µžQÏó.n�·|O¹oµÐsäÑ51ÌTDÌ�áøÙš·žílì�ÏiâÐë,Óùš°)dÓ˜mSÕVunB¹Ù6cÍ¿1š·±�å>�>õe<—¢å|ñtý›S�îÒZÆ ôªLŸ×øÐ?õûWþÊþ{vgò‹…Ø"ïIïŽD"!Šs699)J:øê;‡(A¦9nÿ J�i�.Ãq+Ë�Ä×¢KÞƒŽGDIÝþR”tðÀŠ 1: if unravel_shape is not None: # argreductions only mask = ret == fill_value ret[mask] = 0 ret = np.unravel_index(ret, unravel_shape)[axis] ret[mask] = fill_value ret = ret.reshape(size, order=order) return ret @classmethod def _initialize(cls, flat_size, fill_value, dtype, input_dtype, input_size): if cls.forced_fill_value is None: ret = np.full(flat_size, fill_value, dtype=dtype) else: ret = np.full(flat_size, cls.forced_fill_value, dtype=dtype) counter = mean = outer = None if cls.counter_fill_value is not None: counter = np.full_like(ret, cls.counter_fill_value, dtype=cls.counter_dtype) if cls.mean_fill_value is not None: dtype = cls.mean_dtype if cls.mean_dtype else input_dtype mean = np.full_like(ret, cls.mean_fill_value, dtype=dtype) if cls.outer: outer = np.full(input_size, fill_value, dtype=dtype) return ret, counter, mean, outer @classmethod def _finalize(cls, ret, counter, fill_value): if cls.forced_fill_value is not None and fill_value != cls.forced_fill_value: if cls.counter_dtype == bool: ret[counter] = fill_value else: ret[~counter.astype(bool)] = fill_value @classmethod def callable(cls, nans=False, reverse=False, scalar=False): """Compile a jitted function doing the hard part of the job""" _valgetter = cls._valgetter_scalar if scalar else cls._valgetter valgetter = nb.njit(_valgetter) outersetter = nb.njit(cls._outersetter) if not nans: inner = nb.njit(cls._inner) else: cls_inner = nb.njit(cls._inner) cls_nan_check = nb.njit(cls._nan_check) @nb.njit def inner(ri, val, ret, counter, mean, fill_value): if not cls_nan_check(val): cls_inner(ri, val, ret, counter, mean, fill_value) @nb.njit def loop(group_idx, a, ret, counter, mean, outer, fill_value, ddof): # ddof needs to be present for being exchangeable with loop_2pass size = len(ret) rng = ( range(len(group_idx) - 1, -1, -1) if reverse else range(len(group_idx)) ) for i in rng: ri = group_idx[i] if ri < 0: raise ValueError("negative indices not supported") if ri >= size: raise ValueError("one or more indices in group_idx are too large") val = valgetter(a, i) inner(ri, val, ret, counter, mean, fill_value) outersetter(outer, i, ret[ri]) return loop @staticmethod def _valgetter(a, i): return a[i] @staticmethod def _valgetter_scalar(a, i): return a @staticmethod def _nan_check(val): return val != val @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): raise NotImplementedError("subclasses need to overwrite _inner") @staticmethod def _outersetter(outer, i, val): pass class Aggregate2pass(AggregateOp): """Base class for everything that needs to process the data twice like mean, var and std.""" @classmethod def callable(cls, nans=False, reverse=False, scalar=False): # Careful, cls needs to be passed, so that the overwritten methods remain available in # AggregateOp.callable loop_1st = super().callable(nans=nans, reverse=reverse, scalar=scalar) _2pass_inner = nb.njit(cls._2pass_inner) @nb.njit def loop_2nd(ret, counter, mean, fill_value, ddof): for ri in range(len(ret)): if counter[ri] > ddof: ret[ri] = _2pass_inner(ri, ret, counter, mean, ddof) else: ret[ri] = fill_value @nb.njit def loop_2pass(group_idx, a, ret, counter, mean, outer, fill_value, ddof): loop_1st(group_idx, a, ret, counter, mean, outer, fill_value, ddof) loop_2nd(ret, counter, mean, fill_value, ddof) return loop_2pass @staticmethod def _2pass_inner(ri, ret, counter, mean, ddof): raise NotImplementedError("subclasses need to overwrite _2pass_inner") @classmethod def _finalize(cls, ret, counter, fill_value): """Copying the fill value is already done in the 2nd pass""" pass class AggregateNtoN(AggregateOp): """Base class for cumulative functions, where the output size matches the input size.""" outer = True @staticmethod def _outersetter(outer, i, val): outer[i] = val class AggregateGeneric(AggregateOp): """Base class for jitting arbitrary functions.""" counter_fill_value = None def __init__(self, func, **kwargs): self.func = func self.__dict__.update(kwargs) self._jitfunc = self.callable(self.nans) def __call__( self, group_idx, a, size=None, fill_value=0, order="C", dtype=None, axis=None, ddof=0, ): iv = input_validation( group_idx, a, size=size, order=order, axis=axis, check_bounds=False ) group_idx, a, flat_size, ndim_idx, size, _ = iv # TODO: The typecheck should be done by the class itself, not by check_dtype dtype = check_dtype(dtype, self.func, a, len(group_idx)) check_fill_value(fill_value, dtype, func=self.func) input_dtype = type(a) if np.isscalar(a) else a.dtype ret, _, _, _ = self._initialize( flat_size, fill_value, dtype, input_dtype, group_idx.size ) group_idx = np.ascontiguousarray(group_idx) sortidx = np.argsort(group_idx, kind="mergesort") self._jitfunc(sortidx, group_idx, a, ret) # Deal with ndimensional indexing if ndim_idx > 1: ret = ret.reshape(size, order=order) return ret def callable(self, nans=False): """Compile a jitted function and loop it over the sorted data.""" func = nb.njit(self.func) @nb.njit def loop(sortidx, group_idx, a, ret): size = len(ret) group_idx_srt = group_idx[sortidx] a_srt = a[sortidx] indices = step_indices(group_idx_srt) for i in range(len(indices) - 1): start_idx, stop_idx = indices[i], indices[i + 1] ri = group_idx_srt[start_idx] if ri < 0: raise ValueError("negative indices not supported") if ri >= size: raise ValueError("one or more indices in group_idx are too large") ret[ri] = func(a_srt[start_idx:stop_idx]) return loop class Sum(AggregateOp): forced_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] += val class Prod(AggregateOp): forced_fill_value = 1 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] *= val class Len(AggregateOp): forced_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] += 1 class All(AggregateOp): forced_fill_value = 1 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] &= bool(val) class Any(AggregateOp): forced_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] |= bool(val) class Last(AggregateOp): counter_fill_value = None @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): ret[ri] = val class First(Last): reverse = True class AllNan(AggregateOp): forced_fill_value = 1 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] &= val != val class AnyNan(AggregateOp): forced_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] |= val != val class Max(AggregateOp): @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): if counter[ri]: ret[ri] = val counter[ri] = 0 elif ret[ri] < val: ret[ri] = val class Min(AggregateOp): @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): if counter[ri]: ret[ri] = val counter[ri] = 0 elif ret[ri] > val: ret[ri] = val class ArgMax(AggregateOp): mean_fill_value = np.nan @staticmethod def _valgetter(a, i): return a[i], i @staticmethod def _nan_check(val): return val[0] != val[0] @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): cmp_val, arg = val if counter[ri]: # start of a new group counter[ri] = 0 mean[ri] = cmp_val if cmp_val == cmp_val: # Don't point on nans ret[ri] = arg elif mean[ri] < cmp_val: # larger valid value found mean[ri] = cmp_val ret[ri] = arg elif cmp_val != cmp_val: # nan found, reset group mean[ri] = cmp_val ret[ri] = fill_value class ArgMin(ArgMax): @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): cmp_val, arg = val if counter[ri]: # start of a new group counter[ri] = 0 mean[ri] = cmp_val if cmp_val == cmp_val: # Don't point on nans ret[ri] = arg elif mean[ri] > cmp_val: # larger valid value found mean[ri] = cmp_val ret[ri] = arg elif cmp_val != cmp_val: # nan found, reset group mean[ri] = cmp_val ret[ri] = fill_value class SumOfSquares(AggregateOp): forced_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] = 0 ret[ri] += val * val class Mean(Aggregate2pass): forced_fill_value = 0 counter_fill_value = 0 counter_dtype = int @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] += 1 ret[ri] += val @staticmethod def _2pass_inner(ri, ret, counter, mean, ddof): return ret[ri] / counter[ri] class Std(Mean): mean_fill_value = 0 @staticmethod def _inner(ri, val, ret, counter, mean, fill_value): counter[ri] += 1 mean[ri] += val ret[ri] += val * val @staticmethod def _2pass_inner(ri, ret, counter, mean, ddof): mean2 = mean[ri] * mean[ri] return np.sqrt((ret[ri] - mean2 / counter[ri]) / (counter[ri] - ddof)) class Var(Std): @staticmethod def _2pass_inner(ri, ret, counter, mean, ddof): mean2 = mean[ri] * mean[ri] return (ret[ri] - mean2 / counter[ri]) / (counter[ri] - ddof) class CumSum(AggregateNtoN, Sum): pass class CumProd(AggregateNtoN, Prod): pass class CumMax(AggregateNtoN, Max): pass class CumMin(AggregateNtoN, Min): pass def get_funcs(): funcs = {} for op in ( Sum, Prod, Len, All, Any, Last, First, AllNan, AnyNan, Min, Max, ArgMin, ArgMax, Mean, Std, Var, SumOfSquares, CumSum, CumProd, CumMax, CumMin, ): funcname = op.__name__.lower() funcs[funcname] = op(funcname) if funcname not in funcs_no_separate_nan: funcname = "nan" + funcname funcs[funcname] = op(funcname, nans=True) return funcs _impl_dict = get_funcs() _default_cache = {} def aggregate( group_idx, a, func="sum", size=None, fill_value=0, order="C", dtype=None, axis=None, cache=True, **kwargs, ): func = get_func(func, aliasing, _impl_dict) if not isinstance(func, str): if cache in (None, False): # Keep None and False in order to accept empty dictionaries aggregate_op = AggregateGeneric(func) else: if cache is True: cache = _default_cache aggregate_op = cache.setdefault(func, AggregateGeneric(func)) return aggregate_op( group_idx, a, size, fill_value, order, dtype, axis, **kwargs ) else: func = _impl_dict[func] return func(group_idx, a, size, fill_value, order, dtype, axis, **kwargs) aggregate.__doc__ = ( """ This is the numba implementation of aggregate. """ + aggregate_common_doc ) @nb.njit def step_count(group_idx): """Return the amount of index changes within group_idx.""" cmp_pos = 0 steps = 1 if len(group_idx) < 1: return 0 for i in range(len(group_idx)): if group_idx[cmp_pos] != group_idx[i]: cmp_pos = i steps += 1 return steps @nb.njit def step_indices(group_idx): """Return the edges of areas within group_idx, which are filled with the same value.""" ilen = step_count(group_idx) + 1 indices = np.empty(ilen, np.int64) indices[0] = 0 indices[-1] = group_idx.size cmp_pos = 0 ri = 1 for i in range(len(group_idx)): if group_idx[cmp_pos] != group_idx[i]: cmp_pos = i indices[ri] = i ri += 1 return indices numpy-groupies-0.11.3/numpy_groupies/aggregate_numpy.py000066400000000000000000000312451501360642500234230ustar00rootroot00000000000000import numpy as np from .utils import ( aggregate_common_doc, aliasing, check_boolean, check_dtype, check_fill_value, funcs_no_separate_nan, get_func, input_validation, iscomplexobj, maxval, minimum_dtype, minimum_dtype_scalar, minval, ) def _sum(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype_scalar(fill_value, dtype, a) if np.ndim(a) == 0: ret = np.bincount(group_idx, minlength=size).astype(dtype, copy=False) if a != 1: ret *= a else: if iscomplexobj(a): ret = np.empty(size, dtype=dtype) ret.real = np.bincount(group_idx, weights=a.real, minlength=size) ret.imag = np.bincount(group_idx, weights=a.imag, minlength=size) else: ret = np.bincount(group_idx, weights=a, minlength=size).astype( dtype, copy=False ) if fill_value != 0: _fill_untouched(group_idx, ret, fill_value) return ret def _prod(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype_scalar(fill_value, dtype, a) ret = np.full(size, fill_value, dtype=dtype) if fill_value != 1: ret[group_idx] = 1 # product starts from 1 np.multiply.at(ret, group_idx, a) return ret def _len(group_idx, a, size, fill_value, dtype=None): return _sum(group_idx, 1, size, fill_value, dtype=int) def _last(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype(fill_value, dtype or a.dtype) ret = np.full(size, fill_value, dtype=dtype) # repeated indexing gives last value, see: # the phrase "leaving behind the last value" on this page: # http://wiki.scipy.org/Tentative_NumPy_Tutorial ret[group_idx] = a return ret def _first(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype(fill_value, dtype or a.dtype) ret = np.full(size, fill_value, dtype=dtype) ret[group_idx[::-1]] = a[::-1] # same trick as _last, but in reverse return ret def _all(group_idx, a, size, fill_value, dtype=None): check_boolean(fill_value) ret = np.full(size, fill_value, dtype=bool) if not fill_value: ret[group_idx] = True ret[group_idx.compress(np.logical_not(a))] = False return ret def _any(group_idx, a, size, fill_value, dtype=None): check_boolean(fill_value) ret = np.full(size, fill_value, dtype=bool) if fill_value: ret[group_idx] = False ret[group_idx.compress(a)] = True return ret def _min(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype(fill_value, dtype or a.dtype) dmax = maxval(fill_value, dtype) with np.errstate(invalid="ignore"): ret = np.full(size, fill_value, dtype=dtype) if fill_value != dmax: ret[group_idx] = dmax # min starts from maximum with np.errstate(invalid="ignore"): np.minimum.at(ret, group_idx, a) return ret def _max(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype(fill_value, dtype or a.dtype) dmin = minval(fill_value, dtype) with np.errstate(invalid="ignore"): ret = np.full(size, fill_value, dtype=dtype) if fill_value != dmin: ret[group_idx] = dmin # max starts from minimum with np.errstate(invalid="ignore"): np.maximum.at(ret, group_idx, a) return ret def _argmax(group_idx, a, size, fill_value, dtype=int, _nansqueeze=False): a_ = np.where(np.isnan(a), -np.inf, a) if _nansqueeze else a group_max = _max(group_idx, a_, size, np.nan) # nan should never be maximum, so use a and not a_ is_max = a == group_max[group_idx] ret = np.full(size, fill_value, dtype=dtype) group_idx_max = group_idx[is_max] (argmax,) = is_max.nonzero() ret[group_idx_max[::-1]] = argmax[ ::-1 ] # reverse to ensure first value for each group wins return ret def _argmin(group_idx, a, size, fill_value, dtype=int, _nansqueeze=False): a_ = np.where(np.isnan(a), np.inf, a) if _nansqueeze else a group_min = _min(group_idx, a_, size, np.nan) # nan should never be minimum, so use a and not a_ is_min = a == group_min[group_idx] ret = np.full(size, fill_value, dtype=dtype) group_idx_min = group_idx[is_min] (argmin,) = is_min.nonzero() ret[group_idx_min[::-1]] = argmin[ ::-1 ] # reverse to ensure first value for each group wins return ret def _mean(group_idx, a, size, fill_value, dtype=np.dtype(np.float64)): if np.ndim(a) == 0: raise ValueError("cannot take mean with scalar a") counts = np.bincount(group_idx, minlength=size) if iscomplexobj(a): dtype = a.dtype # TODO: this is a bit clumsy sums = np.empty(size, dtype=dtype) sums.real = np.bincount(group_idx, weights=a.real, minlength=size) sums.imag = np.bincount(group_idx, weights=a.imag, minlength=size) else: sums = np.bincount(group_idx, weights=a, minlength=size) with np.errstate(divide="ignore", invalid="ignore"): ret = sums / counts if not np.isnan(fill_value): ret[counts == 0] = fill_value if iscomplexobj(a): return ret else: return ret.astype(dtype, copy=False) def _sum_of_squres(group_idx, a, size, fill_value, dtype=np.dtype(np.float64)): ret = np.bincount(group_idx, weights=a * a, minlength=size) if fill_value != 0: counts = np.bincount(group_idx, minlength=size) ret[counts == 0] = fill_value if iscomplexobj(a): return ret else: return ret.astype(dtype, copy=False) def _var( group_idx, a, size, fill_value, dtype=np.dtype(np.float64), sqrt=False, ddof=0 ): if np.ndim(a) == 0: raise ValueError("cannot take variance with scalar a") counts = np.bincount(group_idx, minlength=size) sums = np.bincount(group_idx, weights=a, minlength=size) with np.errstate(divide="ignore", invalid="ignore"): means = sums / counts counts = np.where(counts > ddof, counts - ddof, 0) ret = ( np.bincount(group_idx, (a - means[group_idx]) ** 2, minlength=size) / counts ) if sqrt: ret = np.sqrt(ret) # this is now std not var if not np.isnan(fill_value): ret[counts == 0] = fill_value if iscomplexobj(a): return ret else: return ret.astype(dtype, copy=False) def _std(group_idx, a, size, fill_value, dtype=np.dtype(np.float64), ddof=0): return _var(group_idx, a, size, fill_value, dtype=dtype, sqrt=True, ddof=ddof) def _allnan(group_idx, a, size, fill_value, dtype=bool): return _all(group_idx, np.isnan(a), size, fill_value=fill_value, dtype=dtype) def _anynan(group_idx, a, size, fill_value, dtype=bool): return _any(group_idx, np.isnan(a), size, fill_value=fill_value, dtype=dtype) def _sort(group_idx, a, size=None, fill_value=None, dtype=None, reverse=False): sortidx = np.lexsort((-a if reverse else a, group_idx)) # Reverse sorting back to into grouped order, but preserving groupwise sorting revidx = np.argsort(np.argsort(group_idx, kind="mergesort"), kind="mergesort") return a[sortidx][revidx] def _array(group_idx, a, size, fill_value, dtype=None): """groups a into separate arrays, keeping the order intact.""" if fill_value is not None and not (np.isscalar(fill_value) or len(fill_value) == 0): raise ValueError("fill_value must be None, a scalar or an empty sequence") order_group_idx = np.argsort(group_idx, kind="mergesort") counts = np.bincount(group_idx, minlength=size) ret = np.split(a[order_group_idx], np.cumsum(counts)[:-1]) ret = np.asanyarray(ret, dtype="object") if fill_value is None or np.isscalar(fill_value): _fill_untouched(group_idx, ret, fill_value) return ret def _generic_callable( group_idx, a, size, fill_value, dtype=None, func=lambda g: g, **kwargs ): """groups a by inds, and then applies foo to each group in turn, placing the results in an array.""" groups = _array(group_idx, a, size, ()) ret = np.full(size, fill_value, dtype=dtype or np.float64) for i, grp in enumerate(groups): if np.ndim(grp) == 1 and len(grp) > 0: ret[i] = func(grp) return ret def _cumsum(group_idx, a, size, fill_value=None, dtype=None): """ N to N aggregate operation of cumsum. Perform cumulative sum for each group. group_idx = np.array([4, 3, 3, 4, 4, 1, 1, 1, 7, 8, 7, 4, 3, 3, 1, 1]) a = np.array([3, 4, 1, 3, 9, 9, 6, 7, 7, 0, 8, 2, 1, 8, 9, 8]) _cumsum(group_idx, a, np.max(group_idx) + 1) >>> array([ 3, 4, 5, 6, 15, 9, 15, 22, 7, 0, 15, 17, 6, 14, 31, 39]) """ sortidx = np.argsort(group_idx, kind="mergesort") invsortidx = np.argsort(sortidx, kind="mergesort") group_idx_srt = group_idx[sortidx] a_srt = a[sortidx] a_srt_cumsum = np.cumsum(a_srt, dtype=dtype) increasing = np.arange(len(a), dtype=int) group_starts = _min(group_idx_srt, increasing, size, fill_value=0)[group_idx_srt] # First subtract large numbers a_srt_cumsum -= a_srt_cumsum[group_starts] # Then add potentially small numbers a_srt_cumsum += a_srt[group_starts] return a_srt_cumsum[invsortidx] def _nancumsum(group_idx, a, size, fill_value=None, dtype=None): a_nonans = np.where(np.isnan(a), 0, a) group_idx_nonans = np.where( np.isnan(group_idx), np.nanmax(group_idx) + 1, group_idx ) return _cumsum(group_idx_nonans, a_nonans, size, fill_value=fill_value, dtype=dtype) _impl_dict = dict( min=_min, max=_max, sum=_sum, prod=_prod, last=_last, first=_first, all=_all, any=_any, mean=_mean, std=_std, var=_var, anynan=_anynan, allnan=_allnan, sort=_sort, array=_array, argmax=_argmax, argmin=_argmin, len=_len, cumsum=_cumsum, sumofsquares=_sum_of_squres, generic=_generic_callable, ) _impl_dict.update( ("nan" + k, v) for k, v in list(_impl_dict.items()) if k not in funcs_no_separate_nan ) _impl_dict["nancumsum"] = _nancumsum def _aggregate_base( group_idx, a, func="sum", size=None, fill_value=0, order="C", dtype=None, axis=None, _impl_dict=_impl_dict, is_pandas=False, **kwargs, ): iv = input_validation(group_idx, a, size=size, order=order, axis=axis, func=func) group_idx, a, flat_size, ndim_idx, size, unravel_shape = iv if group_idx.dtype == np.dtype("uint64"): # Force conversion to signed int, to avoid issues with bincount etc later group_idx = group_idx.astype(int) func = get_func(func, aliasing, _impl_dict) if not isinstance(func, str): # do simple grouping and execute function in loop ret = _impl_dict.get("generic", _generic_callable)( group_idx, a, flat_size, fill_value, func=func, dtype=dtype, **kwargs ) else: # deal with nans and find the function if func.startswith("nan"): if np.ndim(a) == 0: raise ValueError("nan-version not supported for scalar input.") if "nan" in func: if "arg" in func: kwargs["_nansqueeze"] = True elif "cum" in func: pass else: good = ~np.isnan(a) if "len" not in func or is_pandas: # a is not needed for len, nanlen! a = a[good] group_idx = group_idx[good] dtype = check_dtype(dtype, func, a, flat_size) check_fill_value(fill_value, dtype, func=func) func = _impl_dict[func] ret = func( group_idx, a, flat_size, fill_value=fill_value, dtype=dtype, **kwargs ) # deal with ndimensional indexing if ndim_idx > 1: if unravel_shape is not None: # A negative fill_value cannot, and should not, be unraveled. mask = ret == fill_value ret[mask] = 0 ret = np.unravel_index(ret, unravel_shape)[axis] ret[mask] = fill_value ret = ret.reshape(size, order=order) return ret def aggregate( group_idx, a, func="sum", size=None, fill_value=0, order="C", dtype=None, axis=None, **kwargs, ): return _aggregate_base( group_idx, a, size=size, fill_value=fill_value, order=order, dtype=dtype, func=func, axis=axis, _impl_dict=_impl_dict, **kwargs, ) aggregate.__doc__ = ( """ This is the pure numpy implementation of aggregate. """ + aggregate_common_doc ) def _fill_untouched(idx, ret, fill_value): """any elements of ret not indexed by idx are set to fill_value.""" untouched = np.ones_like(ret, dtype=bool) untouched[idx] = False ret[untouched] = fill_value numpy-groupies-0.11.3/numpy_groupies/aggregate_numpy_ufunc.py000066400000000000000000000070561501360642500246260ustar00rootroot00000000000000import numpy as np from .aggregate_numpy import _aggregate_base from .utils import ( aggregate_common_doc, aliasing, check_boolean, get_func, maxval, minimum_dtype, minimum_dtype_scalar, minval, ) def _anynan(group_idx, a, size, fill_value, dtype=None): return _any(group_idx, np.isnan(a), size, fill_value=fill_value, dtype=dtype) def _allnan(group_idx, a, size, fill_value, dtype=None): return _all(group_idx, np.isnan(a), size, fill_value=fill_value, dtype=dtype) def _any(group_idx, a, size, fill_value, dtype=None): check_boolean(fill_value) ret = np.full(size, fill_value, dtype=bool) if fill_value: ret[group_idx] = False # any-test should start from False np.logical_or.at(ret, group_idx, a) return ret def _all(group_idx, a, size, fill_value, dtype=None): check_boolean(fill_value) ret = np.full(size, fill_value, dtype=bool) if not fill_value: ret[group_idx] = True # all-test should start from True np.logical_and.at(ret, group_idx, a) return ret def _sum(group_idx, a, size, fill_value, dtype=None): dtype = minimum_dtype_scalar(fill_value, dtype, a) ret = np.full(size, fill_value, dtype=dtype) if fill_value != 0: ret[group_idx] = 0 # sums should start at 0 np.add.at(ret, group_idx, a) return ret def _len(group_idx, a, size, fill_value, dtype=None): return _sum(group_idx, 1, size, fill_value, dtype=int) def _prod(group_idx, a, size, fill_value, dtype=None): """Same as aggregate_numpy.py""" dtype = minimum_dtype_scalar(fill_value, dtype, a) ret = np.full(size, fill_value, dtype=dtype) if fill_value != 1: ret[group_idx] = 1 # product should start from 1 np.multiply.at(ret, group_idx, a) return ret def _min(group_idx, a, size, fill_value, dtype=None): """Same as aggregate_numpy.py""" dtype = minimum_dtype(fill_value, dtype or a.dtype) dmax = maxval(fill_value, dtype) ret = np.full(size, fill_value, dtype=dtype) if fill_value != dmax: ret[group_idx] = dmax # min starts from maximum np.minimum.at(ret, group_idx, a) return ret def _max(group_idx, a, size, fill_value, dtype=None): """Same as aggregate_numpy.py""" dtype = minimum_dtype(fill_value, dtype or a.dtype) dmin = minval(fill_value, dtype) ret = np.full(size, fill_value, dtype=dtype) if fill_value != dmin: ret[group_idx] = dmin # max starts from minimum np.maximum.at(ret, group_idx, a) return ret _impl_dict = dict( min=_min, max=_max, sum=_sum, prod=_prod, all=_all, any=_any, allnan=_allnan, anynan=_anynan, len=_len, ) def aggregate( group_idx, a, func="sum", size=None, fill_value=0, order="C", dtype=None, axis=None, **kwargs, ): func = get_func(func, aliasing, _impl_dict) if not isinstance(func, str): raise NotImplementedError("No such ufunc available") return _aggregate_base( group_idx, a, size=size, fill_value=fill_value, order=order, dtype=dtype, func=func, axis=axis, _impl_dict=_impl_dict, **kwargs, ) aggregate.__doc__ = ( """ Unlike ``aggregate_numpy``, which in most cases does some custom optimisations, this version simply uses ``numpy``'s ``ufunc.at``. As of version 1.14 this gives fairly poor performance. There should normally be no need to use this version, it is intended to be used in testing and benchmarking only. """ + aggregate_common_doc ) numpy-groupies-0.11.3/numpy_groupies/aggregate_pandas.py000066400000000000000000000045011501360642500235140ustar00rootroot00000000000000from functools import partial import numpy as np import pandas as pd from .aggregate_numpy import _aggregate_base from .utils import ( aggregate_common_doc, allnan, anynan, check_dtype, funcs_no_separate_nan, ) def _wrapper(group_idx, a, size, fill_value, func="sum", dtype=None, ddof=0, **kwargs): funcname = func.__name__ if callable(func) else func kwargs = {} if funcname in ("var", "std"): kwargs["ddof"] = ddof df = pd.DataFrame({"group_idx": group_idx, "a": a}) if func == "sort": grouped = df.groupby("group_idx", sort=True) else: grouped = df.groupby("group_idx", sort=False).aggregate(func, **kwargs) dtype = check_dtype(dtype, getattr(func, "__name__", funcname), a, size) if funcname.startswith("cum"): ret = grouped.values[:, 0] else: ret = np.full(size, fill_value, dtype=dtype) with np.errstate(invalid="ignore"): ret[grouped.index] = grouped.values[:, 0] return ret _supported_funcs = "sum prod all any min max mean var std first last cumsum cumprod cummax cummin".split() _impl_dict = {fn: partial(_wrapper, func=fn) for fn in _supported_funcs} _impl_dict.update( ("nan" + fn, partial(_wrapper, func=fn)) for fn in _supported_funcs if fn not in funcs_no_separate_nan ) _impl_dict.update( allnan=partial(_wrapper, func=allnan), anynan=partial(_wrapper, func=anynan), len=partial(_wrapper, func="count"), nanlen=partial(_wrapper, func="count"), argmax=partial(_wrapper, func="idxmax"), argmin=partial(_wrapper, func="idxmin"), nanargmax=partial(_wrapper, func="idxmax"), nanargmin=partial(_wrapper, func="idxmin"), generic=_wrapper, ) def aggregate( group_idx, a, func="sum", size=None, fill_value=0, order="C", dtype=None, axis=None, **kwargs, ): return _aggregate_base( group_idx, a, size=size, fill_value=fill_value, order=order, dtype=dtype, func=func, axis=axis, _impl_dict=_impl_dict, is_pandas=True, **kwargs, ) aggregate.__doc__ = ( """ This is the pandas implementation of aggregate. It makes use of `pandas`'s groupby machienery and is mainly used for reference and benchmarking. """ + aggregate_common_doc ) numpy-groupies-0.11.3/numpy_groupies/aggregate_purepy.py000066400000000000000000000111441501360642500235730ustar00rootroot00000000000000import itertools import math import operator from .utils import aggregate_common_doc from .utils import aliasing_py as aliasing from .utils import funcs_no_separate_nan, get_func # min, max, sum, all, any - builtin def _last(x): return x[-1] def _first(x): return x[0] def _array(x): return x def _mean(x): return sum(x) / len(x) def _var(x, ddof=0): mean = _mean(x) return sum((xx - mean) ** 2 for xx in x) / (len(x) - ddof) def _std(x, ddof=0): return math.sqrt(_var(x, ddof=ddof)) def _prod(x): r = x[0] for xx in x[1:]: r *= xx return r def _anynan(x): return any(math.isnan(xx) for xx in x) def _allnan(x): return all(math.isnan(xx) for xx in x) def _argmax(x_and_idx): return max(x_and_idx, key=operator.itemgetter(1))[0] _argmax.x_and_idx = True # tell aggregate what to use as first arg def _argmin(x_and_idx): return min(x_and_idx, key=operator.itemgetter(1))[0] _argmin.x_and_idx = True # tell aggregate what to use as first arg def _sort(group_idx, a, reverse=False): def _argsort(unordered): return sorted(range(len(unordered)), key=lambda k: unordered[k]) sortidx = _argsort( list((gi, aj) for gi, aj in zip(group_idx, -a if reverse else a)) ) revidx = _argsort(_argsort(group_idx)) a_srt = [a[si] for si in sortidx] return [a_srt[ri] for ri in revidx] _impl_dict = dict( min=min, max=max, sum=sum, prod=_prod, last=_last, first=_first, all=all, any=any, mean=_mean, std=_std, var=_var, anynan=_anynan, allnan=_allnan, sort=_sort, array=_array, argmax=_argmax, argmin=_argmin, len=len, ) _impl_dict.update( ("nan" + k, v) for k, v in list(_impl_dict.items()) if k not in funcs_no_separate_nan ) def aggregate( group_idx, a, func="sum", size=None, fill_value=0, order=None, dtype=None, axis=None, **kwargs, ): if axis is not None: raise NotImplementedError("axis arg not supported in purepy implementation.") # Check for 2d group_idx if size is None: try: size = 1 + int(max(group_idx)) except (TypeError, ValueError): raise NotImplementedError( "pure python implementation doesn't accept ndim idx input." ) for i in group_idx: try: i = int(i) except (TypeError, ValueError): if isinstance(i, (list, tuple)): raise NotImplementedError( "pure python implementation doesn't accept ndim idx input." ) else: try: len(i) except TypeError: raise ValueError(f"invalid value found in group_idx: {i}") else: raise NotImplementedError( "pure python implementation doesn't accept ndim indexed input." ) else: if i < 0: raise ValueError("group_idx contains negative value") func = get_func(func, aliasing, _impl_dict) if isinstance(a, (int, float)): if func not in ("sum", "prod", "len"): raise ValueError( "scalar inputs are supported only for 'sum', 'prod' and 'len'" ) a = [a] * len(group_idx) elif len(group_idx) != len(a): raise ValueError("group_idx and a must be of the same length") if isinstance(func, str): if func.startswith("nan"): func = func[3:] # remove nans group_idx, a = zip( *((ix, val) for ix, val in zip(group_idx, a) if not math.isnan(val)) ) func = _impl_dict[func] if func is _sort: return _sort(group_idx, a, reverse=kwargs.get("reverse", False)) # sort data and evaluate function on groups ret = [fill_value] * size if not getattr(func, "x_and_idx", False): data = sorted(zip(group_idx, a), key=operator.itemgetter(0)) for ix, group in itertools.groupby(data, key=operator.itemgetter(0)): ret[ix] = func(list(val for _, val in group), **kwargs) else: data = sorted(zip(range(len(a)), group_idx, a), key=operator.itemgetter(1)) for ix, group in itertools.groupby(data, key=operator.itemgetter(1)): ret[ix] = func(list((val_idx, val) for val_idx, _, val in group), **kwargs) return ret aggregate.__doc__ = ( """ This is the pure python implementation of aggregate. It is terribly slow. Using the numpy version is highly recommended. """ + aggregate_common_doc ) numpy-groupies-0.11.3/numpy_groupies/benchmarks/000077500000000000000000000000001501360642500220035ustar00rootroot00000000000000numpy-groupies-0.11.3/numpy_groupies/benchmarks/__init__.py000066400000000000000000000000001501360642500241020ustar00rootroot00000000000000numpy-groupies-0.11.3/numpy_groupies/benchmarks/generic.py000066400000000000000000000103451501360642500237740ustar00rootroot00000000000000#!/usr/bin/python -B import platform import sys import timeit from operator import itemgetter import numpy as np from numpy_groupies.tests import _implementations, aggregate_numpy from numpy_groupies.utils import allnan, anynan, nanfirst, nanlast def aggregate_grouploop(*args, **kwargs): """wraps func in lambda which prevents aggregate_numpy from recognising and optimising it. Instead it groups and loops.""" extrafuncs = { "allnan": allnan, "anynan": anynan, "first": itemgetter(0), "last": itemgetter(-1), "nanfirst": nanfirst, "nanlast": nanlast, } func = kwargs.pop("func") func = extrafuncs.get(func, func) if isinstance(func, str): raise NotImplementedError("Grouploop needs to be called with a function") return aggregate_numpy.aggregate(*args, func=lambda x: func(x), **kwargs) def arbitrary(iterator): tmp = 0 for i, x in enumerate(iterator, 1): tmp += x**i return tmp func_list = ( np.sum, np.prod, np.min, np.max, len, np.all, np.any, "anynan", "allnan", np.mean, np.std, np.var, "first", "last", "argmax", "argmin", np.nansum, np.nanprod, np.nanmin, np.nanmax, "nanlen", "nanall", "nanany", np.nanmean, np.nanvar, np.nanstd, "nanfirst", "nanlast", "nanargmin", "nanargmax", "cumsum", "cumprod", "cummax", "cummin", arbitrary, "sort", ) def benchmark_data(size=5e5, seed=100): rnd = np.random.RandomState(seed=seed) group_idx = rnd.randint(0, int(1e3), int(size)) a = rnd.random_sample(group_idx.size) a[a > 0.8] = 0 nana = a.copy() nana[(nana < 0.2) & (nana != 0)] = np.nan nan_share = np.mean(np.isnan(nana)) assert 0.15 < nan_share < 0.25, f"{nan_share * 100:3f}% nans" return a, nana, group_idx def benchmark(implementations, repeat=5, size=5e5, seed=100, raise_errors=False): a, nana, group_idx = benchmark_data(size=size, seed=seed) print( "function" + "".join(impl.__name__.rsplit("_", 1)[1].rjust(14) for impl in implementations) ) print("-" * (9 + 14 * len(implementations))) for func in func_list: func_name = getattr(func, "__name__", func) print(func_name.ljust(9), end="") results = [] used_a = nana if "nan" in func_name else a for impl in implementations: if impl is None: print("----".rjust(14), end="") continue aggregatefunc = impl.aggregate try: res = aggregatefunc(group_idx, used_a, func=func) except NotImplementedError: print("----".rjust(14), end="") continue except Exception: if raise_errors: raise print("ERROR".rjust(14), end="") else: results.append(res) try: np.testing.assert_array_almost_equal(res, results[0]) except AssertionError: print("FAIL".rjust(14), end="") else: t0 = min( timeit.Timer( lambda: aggregatefunc(group_idx, used_a, func=func) ).repeat(repeat=repeat, number=1) ) print(f"{t0 * 1000:.3f}".rjust(14), end="") sys.stdout.flush() print() implementation_names = [impl.__name__.rsplit("_", 1)[1] for impl in implementations] postfix = "" if "numba" in implementation_names: import numba postfix += f", Numba {numba.__version__}" if "pandas" in implementation_names: import pandas postfix += f", Pandas {pandas.__version__}" print( f"{platform.system()}({platform.machine()}), Python {sys.version.split()[0]}, Numpy {np.version.version}" f"{postfix}" ) if __name__ == "__main__": implementations = ( _implementations if "--purepy" in sys.argv else _implementations[1:] ) implementations = ( implementations if "--pandas" in sys.argv else implementations[:-1] ) benchmark(implementations, raise_errors=False) numpy-groupies-0.11.3/numpy_groupies/benchmarks/simple.py000066400000000000000000000105471501360642500236550ustar00rootroot00000000000000#!/usr/bin/python -B import timeit import numpy as np from numpy_groupies import aggregate_np, aggregate_py, aggregate_ufunc from numpy_groupies.aggregate_pandas import aggregate as aggregate_pd from numpy_groupies.utils import aliasing def aggregate_group_loop(*args, **kwargs): """wraps func in lambda which prevents aggregate_numpy from recognising and optimising it. Instead it groups and loops.""" func = kwargs["func"] del kwargs["func"] return aggregate_np(*args, func=lambda x: func(x), **kwargs) print("-----simple examples----------") test_a = np.array([12.0, 3.2, -15, 88, 12.9]) test_group_idx = np.array([1, 0, 1, 4, 1]) print("test_a: ", test_a) print("test_group_idx: ", test_group_idx) print("aggregate(test_group_idx, test_a):") print(aggregate_np(test_group_idx, test_a)) # group vals by idx and sum # array([3.2, 9.9, 0., 0., 88.]) print("aggregate(test_group_idx, test_a, sz=8, func='min', fill_value=np.nan):") print(aggregate_np(test_group_idx, test_a, size=8, func="min", fill_value=np.nan)) # array([3.2, -15., nan, 88., nan, nan, nan, nan]) print( "aggregate_py(test_group_idx, test_a, sz=5, func=lambda x: ' + '.join(str(xx) for xx in x),fill_value='')" ) print( aggregate_py( test_group_idx, test_a, size=5, func=lambda x: " + ".join(str(xx) for xx in x), fill_value="", ) ) print("") print("---------testing--------------") print("compare against group-and-loop with numpy") testable_funcs = { aliasing[f]: f for f in (np.sum, np.prod, np.any, np.all, np.min, np.max, np.std, np.var, np.mean) } test_group_idx = np.random.randint(0, int(1e3), int(1e5)) test_a = np.random.rand(int(1e5)) * 100 - 50 test_a[test_a > 25] = 0 # for use with bool functions for name, f in testable_funcs.items(): numpy_loop_group = aggregate_group_loop(test_group_idx, test_a, func=f) for acc_func, acc_name in [ (aggregate_np, "np-optimised"), (aggregate_ufunc, "np-ufunc-at"), (aggregate_py, "purepy"), (aggregate_pd, "pandas"), ]: try: test_out = acc_func(test_group_idx, test_a, func=name) test_out = np.asarray(test_out) if not np.allclose(test_out, numpy_loop_group.astype(test_out.dtype)): print( name, acc_name, "FAILED test, output: [" + acc_name + "; correct]...", ) print(np.vstack((test_out, numpy_loop_group))) else: print(name, acc_name, "PASSED test") except NotImplementedError: print(name, acc_name, "NOT IMPLEMENTED") print("") print("----------benchmarking-------------") print( "Note that the actual observed speedup depends on a variety of properties of the input." ) print("Here we are using 100,000 indices uniformly picked from [0, 1000).") print("Specifically, about 25% of the values are 0 (for use with bool operations),") print("the remainder are uniformly distributed on [-50,25).") print("Times are scaled to 10 repetitions (actual number of reps used may not be 10).") print( "".join( [ "function".rjust(8), "pure-py".rjust(14), "np-grouploop".rjust(14), "np-ufuncat".rjust(14), "np-optimised".rjust(14), "pandas".rjust(14), "ratio".rjust(15), ] ) ) for name, f in testable_funcs.items(): print(name.rjust(8), end="") times = [None] * 5 for ii, acc_func in enumerate( [ aggregate_py, aggregate_group_loop, aggregate_ufunc, aggregate_np, aggregate_pd, ] ): try: func = f if acc_func is aggregate_group_loop else name reps = 3 if acc_func is aggregate_py else 20 times[ii] = ( timeit.Timer( lambda: acc_func(test_group_idx, test_a, func=func) ).timeit(number=reps) / reps * 10 ) print(f"{times[ii] * 1000:.1f}ms".rjust(13), end="") except NotImplementedError: print("no-impl".rjust(13), end="") denom = min(t for t in times if t is not None) ratios = [ ("-".center(4) if t is None else str(round(t / denom, 1))).center(5) for t in times ] print(" ", (":".join(ratios))) numpy-groupies-0.11.3/numpy_groupies/tests/000077500000000000000000000000001501360642500210305ustar00rootroot00000000000000numpy-groupies-0.11.3/numpy_groupies/tests/__init__.py000066400000000000000000000053301501360642500231420ustar00rootroot00000000000000from functools import wraps import pytest from .. import aggregate_numpy, aggregate_numpy_ufunc, aggregate_purepy try: from .. import aggregate_numba except ImportError: aggregate_numba = None try: from .. import aggregate_pandas except ImportError: aggregate_pandas = None _implementations = [ aggregate_purepy, aggregate_numpy_ufunc, aggregate_numpy, aggregate_numba, aggregate_pandas, ] _implementations = [i for i in _implementations if i is not None] def _impl_name(impl): if not impl or type(impl).__name__ == "NotSetType": return return impl.__name__.rsplit("aggregate_", 1)[1].rsplit("_", 1)[-1] _implemented_by_impl_name = { "numpy": {"not_implemented": ("cumprod", "cummax", "cummin")}, "purepy": { "not_implemented": ("cumsum", "cumprod", "cummax", "cummin", "sumofsquares") }, "numba": {"not_implemented": ("array", "list", "sort")}, "pandas": { "not_implemented": ("array", "list", "sort", "sumofsquares", "nansumofsquares") }, "ufunc": { "implemented": ( "sum", "prod", "min", "max", "len", "all", "any", "anynan", "allnan", ) }, } def _is_implemented(impl_name, funcname): func_description = _implemented_by_impl_name[impl_name] not_implemented = func_description.get("not_implemented", []) implemented = func_description.get("implemented", []) if impl_name == "purepy" and funcname.startswith("nan"): return False if funcname in not_implemented: return False if implemented and funcname not in implemented: return False return True def _wrap_notimplemented_skip(impl, name=None): """Some implementations lack some functionality. That's ok, let's skip that instead of raising errors.""" @wraps(impl) def try_skip(*args, **kwargs): try: return impl(*args, **kwargs) except NotImplementedError: impl_name = impl.__module__.split("_")[-1] func = kwargs.pop("func", None) if callable(func): func = func.__name__ if not _is_implemented(impl_name, func): pytest.skip("Functionality not implemented") if name: try_skip.__name__ = name return try_skip func_list = ( "sum", "prod", "min", "max", "all", "any", "mean", "std", "var", "len", "argmin", "argmax", "anynan", "allnan", "cumsum", "sumofsquares", "nansum", "nanprod", "nanmin", "nanmax", "nanmean", "nanstd", "nanvar", "nanlen", "nanargmin", "nanargmax", "nansumofsquares", ) numpy-groupies-0.11.3/numpy_groupies/tests/test_compare.py000066400000000000000000000123031501360642500240660ustar00rootroot00000000000000""" In this test, aggregate_numpy is taken as a reference implementation and this results are compared against the results of the other implementations. Implementations may throw NotImplementedError in order to show missing functionality without throwing test errors. """ from itertools import product import numpy as np import pytest from . import ( _impl_name, _is_implemented, aggregate_numba, aggregate_numpy, aggregate_numpy_ufunc, aggregate_pandas, aggregate_purepy, func_list, ) class AttrDict(dict): __getattr__ = dict.__getitem__ TEST_PAIRS = ["np/py", "ufunc/np", "numba/np", "pandas/np"] @pytest.fixture(params=TEST_PAIRS, scope="module") def aggregate_cmp(request, seed=100): test_pair = request.param if test_pair == "np/py": # Some functions in purepy are not implemented func_ref = aggregate_purepy.aggregate func = aggregate_numpy.aggregate group_cnt = 100 else: group_cnt = 1000 func_ref = aggregate_numpy.aggregate if "ufunc" in request.param: impl = aggregate_numpy_ufunc elif "numba" in request.param: impl = aggregate_numba elif "pandas" in request.param: impl = aggregate_pandas else: impl = None if not impl: pytest.skip("Implementation not available") name = _impl_name(impl) func = impl.aggregate rnd = np.random.RandomState(seed=seed) # Gives 100000 duplicates of size 10 each group_idx = np.repeat(np.arange(group_cnt), 2) rnd.shuffle(group_idx) group_idx = np.repeat(group_idx, 10) a = rnd.randn(group_idx.size) nana = a.copy() nana[::3] = np.nan nana[: (len(nana) // 2)] = np.nan somea = a.copy() somea[somea < 0.3] = 0 somea[::31] = np.nan return AttrDict(locals()) def _deselect_purepy(aggregate_cmp, *args, **kwargs): # purepy implementation does not handle ndim arrays # This is a won't fix and should be deselected instead of skipped return aggregate_cmp.endswith("py") def _deselect_not_implemented(aggregate_cmp, func, fill_value, *args, **kwargs): impl_name = ( "purepy" if aggregate_cmp.endswith("py") else aggregate_cmp.split("/", 1)[0] ) funcname = getattr(func, "__name__", func) return not _is_implemented(impl_name, funcname) def func_arbitrary(iterator): tmp = 0 for x in iterator: tmp += x * x return tmp def func_preserve_order(iterator): tmp = 0 for i, x in enumerate(iterator, 1): tmp += x**i return tmp @pytest.mark.filterwarnings("ignore::FutureWarning") # handled pandas deprecation @pytest.mark.filterwarnings("ignore:numpy.ufunc size changed") @pytest.mark.deselect_if(func=_deselect_not_implemented) @pytest.mark.parametrize("fill_value", [0, 1, np.nan]) @pytest.mark.parametrize("func", func_list, ids=lambda x: getattr(x, "__name__", x)) def test_cmp(aggregate_cmp, func, fill_value, decimal=10): is_nanfunc = "nan" in getattr(func, "__name__", func) a = aggregate_cmp.nana if is_nanfunc else aggregate_cmp.a try: ref = aggregate_cmp.func_ref( aggregate_cmp.group_idx, a, func=func, fill_value=fill_value ) except ValueError: with pytest.raises(ValueError): aggregate_cmp.func( aggregate_cmp.group_idx, a, func=func, fill_value=fill_value ) else: try: res = aggregate_cmp.func( aggregate_cmp.group_idx, a, func=func, fill_value=fill_value ) except ValueError: if np.isnan(fill_value) and aggregate_cmp.test_pair.endswith("py"): pytest.skip( "pure python version uses lists and does not raise ValueErrors when inserting nan into integers" ) elif aggregate_cmp.test_pair.startswith("pandas"): pytest.skip("pandas now raises ValueError on all-nan arrays") else: raise if isinstance(ref, np.ndarray): assert res.dtype == ref.dtype try: np.testing.assert_allclose(res, ref, rtol=10**-decimal) except AssertionError: if "arg" in func and aggregate_cmp.test_pair.startswith("pandas"): pytest.skip( "pandas doesn't fill indices for all-nan groups with fill_value, but with -inf instead" ) else: raise @pytest.mark.deselect_if(func=_deselect_purepy) @pytest.mark.parametrize(["ndim", "order"], product([2, 3], ["C", "F"])) def test_cmp_ndim(aggregate_cmp, ndim, order, outsize=100, decimal=14): nindices = int(outsize**ndim) outshape = tuple([outsize] * ndim) group_idx = np.random.randint(0, outsize, size=(ndim, nindices)) a = np.random.random(group_idx.shape[1]) res = aggregate_cmp.func(group_idx, a, size=outshape, order=order) ref = aggregate_cmp.func_ref(group_idx, a, size=outshape, order=order) if ndim > 1 and order == "F": # 1d arrays always return False here assert np.isfortran(res) else: assert not np.isfortran(res) assert res.shape == outshape np.testing.assert_array_almost_equal(res, ref, decimal=decimal) numpy-groupies-0.11.3/numpy_groupies/tests/test_generic.py000066400000000000000000000501131501360642500240550ustar00rootroot00000000000000"""Tests, that are run against all implemented versions of aggregate.""" import itertools import warnings import numpy as np import pytest from . import ( _impl_name, _implementations, _wrap_notimplemented_skip, func_list, _is_implemented, ) @pytest.fixture(params=_implementations, ids=_impl_name) def aggregate_all(request): impl = request.param if impl is None: pytest.skip("Implementation not available") name = _impl_name(impl) return _wrap_notimplemented_skip(impl.aggregate, "aggregate_" + name) def _deselect_purepy(aggregate_all, *args, **kwargs): # purepy implementations does not handle nan values and ndim correctly. # So it needs to be excluded from several tests.""" return aggregate_all.__name__.endswith("purepy") def _deselect_purepy_and_pandas(aggregate_all, *args, **kwargs): # purepy and pandas implementation handle some nan cases differently. # So they need to be excluded from several tests.""" return aggregate_all.__name__.endswith(("pandas", "purepy")) def _deselect_purepy_and_invalid_axis(aggregate_all, func, size, axis): impl_name = aggregate_all.__name__.split("_")[-1] if impl_name == "purepy": # purepy does not handle axis parameter return True if axis >= len(size): return True if not _is_implemented(impl_name, func): return True return False def _deselect_not_implemented(aggregate_all, func, *args, **kwargs): impl_name = aggregate_all.__name__.split("_")[-1] return not _is_implemented(impl_name, func) def test_preserve_missing(aggregate_all): res = aggregate_all(np.array([0, 1, 3, 1, 3]), np.arange(101, 106, dtype=int)) np.testing.assert_array_equal(res, np.array([101, 206, 0, 208])) if not isinstance(res, list): assert "int" in res.dtype.name @pytest.mark.parametrize("group_idx_type", [int, "uint32", "uint64"]) def test_uint_group_idx(aggregate_all, group_idx_type): group_idx = np.array([1, 1, 2, 2, 2, 2, 4, 4], dtype=group_idx_type) res = aggregate_all(group_idx, np.ones(group_idx.size), dtype=int) np.testing.assert_array_equal(res, np.array([0, 2, 4, 0, 2])) if not isinstance(res, list): assert "int" in res.dtype.name def test_start_with_offset(aggregate_all): group_idx = np.array([1, 1, 2, 2, 2, 2, 4, 4]) res = aggregate_all(group_idx, np.ones(group_idx.size), dtype=int) np.testing.assert_array_equal(res, np.array([0, 2, 4, 0, 2])) if not isinstance(res, list): assert "int" in res.dtype.name @pytest.mark.parametrize( "floatfunc", [np.std, np.var, np.mean], ids=lambda x: x.__name__ ) def test_float_enforcement(aggregate_all, floatfunc): group_idx = np.arange(10).repeat(3) a = np.arange(group_idx.size) res = aggregate_all(group_idx, a, floatfunc) if not isinstance(res, list): assert "float" in res.dtype.name assert np.all(np.array(res) > 0) def test_start_with_offset_prod(aggregate_all): group_idx = np.array([2, 2, 4, 4, 4, 7, 7, 7]) res = aggregate_all(group_idx, group_idx, func=np.prod, dtype=int) np.testing.assert_array_equal(res, np.array([0, 0, 4, 0, 64, 0, 0, 343])) def test_no_negative_indices(aggregate_all): for pos in (0, 10, -1): group_idx = np.arange(5).repeat(5) group_idx[pos] = -1 pytest.raises(ValueError, aggregate_all, group_idx, np.arange(len(group_idx))) def test_parameter_missing(aggregate_all): pytest.raises(TypeError, aggregate_all, np.arange(5)) def test_shape_mismatch(aggregate_all): pytest.raises(ValueError, aggregate_all, np.array((1, 2, 3)), np.array((1, 2))) def test_create_lists(aggregate_all): res = aggregate_all( np.array([0, 1, 3, 1, 3]), np.arange(101, 106, dtype=int), func=list ) np.testing.assert_array_equal(np.array(res[0]), np.array([101])) assert res[2] == 0 np.testing.assert_array_equal(np.array(res[3]), np.array([103, 105])) def test_item_counting(aggregate_all): group_idx = np.array([0, 1, 2, 3, 3, 3, 3, 4, 5, 5, 5, 6, 5, 4, 3, 8, 8]) a = np.arange(group_idx.size) res = aggregate_all(group_idx, a, func=lambda x: len(x) > 1) np.testing.assert_array_equal(res, np.array([0, 0, 0, 1, 1, 1, 0, 0, 1])) @pytest.mark.parametrize( ["func", "fill_value"], [(np.array, None), (np.sum, -1)], ids=["array", "sum"] ) def test_fill_value(aggregate_all, func, fill_value): group_idx = np.array([0, 2, 2], dtype=int) res = aggregate_all( group_idx, np.arange(len(group_idx), dtype=int), func=func, fill_value=fill_value, ) assert res[1] == fill_value @pytest.mark.parametrize("order", ["C", "F"]) def test_array_ordering(aggregate_all, order, size=10): mat = np.zeros((size, size), order=order, dtype=float) mat.flat[:] = np.arange(size * size) assert aggregate_all(np.zeros(size, dtype=int), mat[0, :], order=order)[0] == sum( range(size) ) @pytest.mark.deselect_if(func=_deselect_purepy) @pytest.mark.parametrize("size", [None, (10, 2)]) def test_ndim_group_idx(aggregate_all, size): group_idx = np.vstack((np.repeat(np.arange(10), 10), np.repeat([0, 1], 50))) aggregate_all(group_idx, 1, size=size) @pytest.mark.deselect_if(func=_deselect_purepy) @pytest.mark.parametrize(["ndim", "order"], itertools.product([1, 2, 3], ["C", "F"])) def test_ndim_indexing(aggregate_all, ndim, order, outsize=10): nindices = int(outsize**ndim) outshape = tuple([outsize] * ndim) group_idx = np.random.randint(0, outsize, size=(ndim, nindices)) a = np.random.random(group_idx.shape[1]) res = aggregate_all(group_idx, a, size=outshape, order=order) if ndim > 1 and order == "F": # 1d arrays always return False here assert np.isfortran(res) else: assert not np.isfortran(res) assert res.shape == outshape def test_len(aggregate_all, group_size=5): group_idx = np.arange(0, 100, 2, dtype=int).repeat(group_size) a = np.arange(group_idx.size) res = aggregate_all(group_idx, a, func="len") ref = aggregate_all(group_idx, 1, func="sum") if isinstance(res, np.ndarray): assert issubclass(res.dtype.type, np.integer) else: assert isinstance(res[0], int) np.testing.assert_array_equal(res, ref) group_idx = np.arange(0, 100, dtype=int).repeat(group_size) a = np.arange(group_idx.size) res = aggregate_all(group_idx, a, func=len) if isinstance(res, np.ndarray): assert np.all(res == group_size) else: assert all(x == group_size for x in res) def test_nan_len(aggregate_all): group_idx = np.arange(0, 20, 2, dtype=int).repeat(5) a = np.random.random(group_idx.size) a[::4] = np.nan a[::5] = np.nan res = aggregate_all(group_idx, a, func="nanlen") ref = aggregate_all(group_idx[~np.isnan(a)], 1, func="sum") if isinstance(res, np.ndarray): assert issubclass(res.dtype.type, np.integer) else: assert isinstance(res[0], int) np.testing.assert_array_equal(res, ref) @pytest.mark.parametrize("first_last", ["first", "last"]) def test_first_last(aggregate_all, first_last): group_idx = np.arange(0, 100, 2, dtype=int).repeat(5) a = np.arange(group_idx.size) res = aggregate_all(group_idx, a, func=first_last, fill_value=-1) ref = np.zeros(np.max(group_idx) + 1) ref.fill(-1) ref[::2] = np.arange( 0 if first_last == "first" else 4, group_idx.size, 5, dtype=int ) np.testing.assert_array_equal(res, ref) @pytest.mark.parametrize( ["first_last", "nanoffset"], itertools.product(["nanfirst", "nanlast"], [0, 2, 4]) ) def test_nan_first_last(aggregate_all, first_last, nanoffset): group_idx = np.arange(0, 100, 2, dtype=int).repeat(5) a = np.arange(group_idx.size, dtype=float) a[nanoffset::5] = np.nan res = aggregate_all(group_idx, a, func=first_last, fill_value=-1) ref = np.zeros(np.max(group_idx) + 1) ref.fill(-1) if first_last == "nanfirst": ref_offset = 1 if nanoffset == 0 else 0 else: ref_offset = 3 if nanoffset == 4 else 4 ref[::2] = np.arange(ref_offset, group_idx.size, 5, dtype=int) np.testing.assert_array_equal(res, ref) @pytest.mark.parametrize(["func", "ddof"], itertools.product(["var", "std"], [0, 1, 2])) def test_ddof(aggregate_all, func, ddof, size=20): group_idx = np.zeros(20, dtype=int) a = np.random.random(group_idx.size) res = aggregate_all(group_idx, a, func, ddof=ddof) ref_func = {"std": np.std, "var": np.var}.get(func) ref = ref_func(a, ddof=ddof) assert abs(res[0] - ref) < 1e-10 @pytest.mark.parametrize("func", ["sum", "prod", "mean", "var", "std"]) def test_scalar_input(aggregate_all, func): group_idx = np.arange(0, 100, dtype=int).repeat(5) if func not in ("sum", "prod"): pytest.raises( (ValueError, NotImplementedError), aggregate_all, group_idx, 1, func=func ) else: res = aggregate_all(group_idx, 1, func=func) ref = aggregate_all(group_idx, np.ones_like(group_idx, dtype=int), func=func) np.testing.assert_array_equal(res, ref) @pytest.mark.parametrize("func", ["sum", "prod", "mean", "var", "std", "all", "any"]) def test_nan_input(aggregate_all, func, groups=100): if aggregate_all.__name__.endswith("pandas"): pytest.skip("pandas always skips nan values") group_idx = np.arange(0, groups, dtype=int).repeat(5) a = np.random.random(group_idx.size) a[::2] = np.nan if func in ("all", "any"): ref = np.ones(groups, dtype=bool) else: ref = np.full(groups, np.nan, dtype=float) res = aggregate_all(group_idx, a, func=func) np.testing.assert_array_equal(res, ref) def test_nan_input_len(aggregate_all, groups=100, group_size=5): if aggregate_all.__name__.endswith("pandas"): pytest.skip("pandas always skips nan values") group_idx = np.arange(0, groups, dtype=int).repeat(group_size) a = np.random.random(len(group_idx)) a[::2] = np.nan ref = np.full(groups, group_size, dtype=int) res = aggregate_all(group_idx, a, func=len) np.testing.assert_array_equal(res, ref) def test_argmin_argmax_nonans(aggregate_all): group_idx = np.array([0, 0, 0, 0, 3, 3, 3, 3]) a = np.array([4, 4, 3, 1, 10, 9, 9, 11]) res = aggregate_all(group_idx, a, func="argmax", fill_value=-1) np.testing.assert_array_equal(res, [0, -1, -1, 7]) res = aggregate_all(group_idx, a, func="argmin", fill_value=-1) np.testing.assert_array_equal(res, [3, -1, -1, 5]) @pytest.mark.deselect_if(func=_deselect_purepy) def test_argmin_argmax_nans(aggregate_all): if aggregate_all.__name__.endswith("pandas"): pytest.skip("pandas always ignores nans") group_idx = np.array([0, 0, 0, 0, 3, 3, 3, 3]) a = np.array([4, 4, 3, 1, np.nan, 1, 2, 3]) res = aggregate_all(group_idx, a, func="argmax", fill_value=-1) np.testing.assert_array_equal(res, [0, -1, -1, -1]) res = aggregate_all(group_idx, a, func="argmin", fill_value=-1) np.testing.assert_array_equal(res, [3, -1, -1, -1]) @pytest.mark.deselect_if(func=_deselect_purepy) def test_nanargmin_nanargmax_nans(aggregate_all): if aggregate_all.__name__.endswith("pandas"): pytest.skip( "pandas doesn't fill indices for all-nan groups with fill_value but with -inf instead" ) group_idx = np.array([0, 0, 0, 0, 3, 3, 3, 3]) a = np.array([4, 4, np.nan, 1, np.nan, np.nan, np.nan, np.nan]) res = aggregate_all(group_idx, a, func="nanargmax", fill_value=-1) np.testing.assert_array_equal(res, [0, -1, -1, -1]) res = aggregate_all(group_idx, a, func="nanargmin", fill_value=-1) np.testing.assert_array_equal(res, [3, -1, -1, -1]) def test_nanargmin_nanargmax_nonans(aggregate_all): group_idx = np.array([0, 0, 0, 0, 3, 3, 3, 3]) a = np.array([4, 4, 3, 1, 10, 9, 9, 11]) res = aggregate_all(group_idx, a, func="nanargmax", fill_value=-1) np.testing.assert_array_equal(res, [0, -1, -1, 7]) res = aggregate_all(group_idx, a, func="nanargmin", fill_value=-1) np.testing.assert_array_equal(res, [3, -1, -1, 5]) def test_min_max_inf(aggregate_all): # https://github.com/ml31415/numpy-groupies/issues/40 res = aggregate_all( np.array([0, 1, 2, 0, 1, 2]), np.array([-np.inf, 0, -np.inf, -np.inf, 0, 0]), func="max", ) np.testing.assert_array_equal(res, [-np.inf, 0, 0]) res = aggregate_all( np.array([0, 1, 2, 0, 1, 2]), np.array([np.inf, 0, np.inf, np.inf, 0, 0]), func="min", ) np.testing.assert_array_equal(res, [np.inf, 0, 0]) def test_argmin_argmax_inf(aggregate_all): # https://github.com/ml31415/numpy-groupies/issues/40 res = aggregate_all( np.array([0, 1, 2, 0, 1, 2]), np.array([-np.inf, 0, -np.inf, -np.inf, 0, 0]), func="argmax", fill_value=-1, ) np.testing.assert_array_equal(res, [0, 1, 5]) res = aggregate_all( np.array([0, 1, 2, 0, 1, 2]), np.array([np.inf, 0, np.inf, np.inf, 0, 0]), func="argmin", fill_value=-1, ) np.testing.assert_array_equal(res, [0, 1, 5]) def test_mean(aggregate_all): group_idx = np.array([0, 0, 0, 0, 3, 3, 3, 3]) a = np.arange(len(group_idx)) res = aggregate_all(group_idx, a, func="mean") np.testing.assert_array_equal(res, [1.5, 0, 0, 5.5]) def test_cumsum(aggregate_all): group_idx = np.array([4, 3, 3, 4, 4, 1, 1, 1, 7, 8, 7, 4, 3, 3, 1, 1]) a = np.array([3, 4, 1, 3, 9, 9, 6, 7, 7, 0, 8, 2, 1, 8, 9, 8]) ref = np.array([3, 4, 5, 6, 15, 9, 15, 22, 7, 0, 15, 17, 6, 14, 31, 39]) res = aggregate_all(group_idx, a, func="cumsum") np.testing.assert_array_equal(res, ref) @pytest.mark.deselect_if(func=_deselect_purepy_and_pandas) def test_nancumsum(aggregate_all): # https://github.com/ml31415/numpy-groupies/issues/79 group_idx = [0, 0, 0, 1, 1, 0, 0] a = [2, 2, np.nan, 2, 2, 2, 2] ref = [2.0, 4.0, 4.0, 2.0, 4.0, 6.0, 8.0] res = aggregate_all(group_idx, a, func="nancumsum") np.testing.assert_array_equal(res, ref) def test_cummax(aggregate_all): group_idx = np.array([4, 3, 3, 4, 4, 1, 1, 1, 7, 8, 7, 4, 3, 3, 1, 1]) a = np.array([3, 4, 1, 3, 9, 9, 6, 7, 7, 0, 8, 2, 1, 8, 9, 8]) ref = np.array([3, 4, 4, 3, 9, 9, 9, 9, 7, 0, 8, 9, 4, 8, 9, 9]) res = aggregate_all(group_idx, a, func="cummax") np.testing.assert_array_equal(res, ref) @pytest.mark.parametrize("order", ["normal", "reverse"]) def test_list_ordering(aggregate_all, order): group_idx = np.repeat(np.arange(5), 4) a = np.arange(group_idx.size) if order == "reverse": a = a[::-1] ref = a[:4] res = aggregate_all(group_idx, a, func=list) np.testing.assert_array_equal(np.array(res[0]), ref) @pytest.mark.parametrize("order", ["normal", "reverse"]) def test_sort(aggregate_all, order): group_idx = np.array([3, 3, 3, 2, 2, 2, 1, 1, 1]) a = np.array([3, 2, 1, 3, 4, 5, 5, 10, 1]) ref_normal = np.array([1, 2, 3, 3, 4, 5, 1, 5, 10]) ref_reverse = np.array([3, 2, 1, 5, 4, 3, 10, 5, 1]) reverse = order == "reverse" ref = ref_reverse if reverse else ref_normal res = aggregate_all(group_idx, a, func="sort", reverse=reverse) np.testing.assert_array_equal(res, ref) @pytest.mark.deselect_if(func=_deselect_purepy_and_invalid_axis) @pytest.mark.parametrize("axis", (0, 1)) @pytest.mark.parametrize("size", ((12,), (12, 5))) @pytest.mark.parametrize("func", func_list) def test_along_axis(aggregate_all, func, size, axis): group_idx = np.zeros(size[axis], dtype=int) a = np.random.randn(*size) # add some NaNs to test out nan-skipping if "nan" in func and "nanarg" not in func: a[[1, 4, 5], ...] = np.nan elif "nanarg" in func and a.ndim > 1: a[[1, 4, 5], 1] = np.nan if func in ["any", "all"]: a = a > 0.5 # construct expected values for all cases if func == "len": expected = np.array(size[axis]) elif func == "nanlen": expected = np.array((~np.isnan(a)).sum(axis=axis)) elif func == "anynan": expected = np.isnan(a).any(axis=axis) elif func == "allnan": expected = np.isnan(a).all(axis=axis) elif func == "sumofsquares": expected = np.sum(a * a, axis=axis) elif func == "nansumofsquares": expected = np.nansum(a * a, axis=axis) else: with warnings.catch_warnings(): # Filter expected warnings: # - RuntimeWarning: All-NaN slice encountered # - RuntimeWarning: Mean of empty slice # - RuntimeWarning: Degrees of freedom <= 0 for slice. warnings.simplefilter("ignore", RuntimeWarning) expected = getattr(np, func)(a, axis=axis) # The default fill_value is 0, the following makes the output match numpy fill_value = { "nanprod": 1, "nanvar": np.nan, "nanstd": np.nan, "nanmax": np.nan, "nanmin": np.nan, "nanmean": np.nan, }.get(func, 0) actual = aggregate_all(group_idx, a, axis=axis, func=func, fill_value=fill_value) assert actual.ndim == a.ndim # argmin, argmax don't support keepdims, so we can't use that to construct expected # instead we squeeze out the extra dims in actual. np.testing.assert_allclose(actual.squeeze(), expected) @pytest.mark.deselect_if(func=_deselect_purepy) def test_not_last_axis_reduction(aggregate_all): group_idx = np.array([1, 2, 2, 0, 1]) a = np.array([[1.0, 2.0], [4.0, 4.0], [5.0, 2.0], [np.nan, 3.0], [8.0, 7.0]]) func = "nanmax" fill_value = np.nan axis = 0 actual = aggregate_all(group_idx, a, axis=axis, func=func, fill_value=fill_value) expected = np.array([[np.nan, 3.0], [8.0, 7.0], [5.0, 4.0]]) np.testing.assert_allclose(expected, actual) @pytest.mark.deselect_if(func=_deselect_purepy) def test_custom_callable(aggregate_all): def custom_callable(x): return x.sum() size = (10,) axis = -1 group_idx = np.zeros(size, dtype=int) a = np.random.randn(*size) expected = a.sum(axis=axis, keepdims=True) actual = aggregate_all(group_idx, a, axis=axis, func=custom_callable, fill_value=0) assert actual.ndim == a.ndim np.testing.assert_allclose(actual, expected) @pytest.mark.deselect_if(func=_deselect_purepy) def test_argreduction_nD_array_1D_idx(aggregate_all): # https://github.com/ml31415/numpy-groupies/issues/41 group_idx = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0], dtype=int) a = np.array([[1] * 12, [1] * 12]) actual = aggregate_all(group_idx, a, axis=-1, func="argmax") expected = np.array([[0, 5, 2], [0, 5, 2]]) np.testing.assert_equal(actual, expected) @pytest.mark.deselect_if(func=_deselect_purepy) def test_argreduction_negative_fill_value(aggregate_all): if aggregate_all.__name__.endswith("pandas"): pytest.skip("pandas always skips nan values") group_idx = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0], dtype=int) a = np.array([[1] * 12, [np.nan] * 12]) actual = aggregate_all(group_idx, a, axis=-1, fill_value=-1, func="argmax") expected = np.array([[0, 5, 2], [-1, -1, -1]]) np.testing.assert_equal(actual, expected) @pytest.mark.deselect_if(func=_deselect_not_implemented) @pytest.mark.parametrize( "nan_inds", (None, tuple([[1, 4, 5], Ellipsis]), tuple((1, (0, 1, 2, 3)))) ) @pytest.mark.parametrize("ddof", (0, 1)) @pytest.mark.parametrize("func", ("nanvar", "nanstd")) def test_var_with_nan_fill_value(aggregate_all, ddof, nan_inds, func): a = np.ones((12, 5)) group_idx = np.zeros(a.shape[-1:], dtype=int) if nan_inds is not None: a[nan_inds] = np.nan with warnings.catch_warnings(): # Filter RuntimeWarning: Degrees of freedom <= 0 for slice. warnings.simplefilter("ignore", RuntimeWarning) expected = getattr(np, func)(a, keepdims=True, axis=-1, ddof=ddof) actual = aggregate_all( group_idx, a, axis=-1, fill_value=np.nan, func=func, ddof=ddof ) np.testing.assert_equal(actual, expected) def test_cumsum_accuracy(aggregate_all): array = np.array( [0.00000000e00, 0.00000000e00, 0.00000000e00, 3.27680000e04, 9.99999975e-06] ) group_idx = np.array([0, 0, 0, 0, 1]) actual = aggregate_all(group_idx, array, axis=-1, func="cumsum") expected = array np.testing.assert_allclose(actual, expected) numpy-groupies-0.11.3/numpy_groupies/tests/test_indices.py000066400000000000000000000017711501360642500240650ustar00rootroot00000000000000import numpy as np import pytest from . import _impl_name, aggregate_numba _implementations = [aggregate_numba] _implementations = [i for i in _implementations if i is not None] @pytest.fixture(params=_implementations, ids=_impl_name) def aggregate_nb_wv(request): if request.param is None: pytest.skip("Implementation not available") return request.param def test_step_indices_length(aggregate_nb_wv): group_idx = np.array([1, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2], dtype=int) for _ in range(20): np.random.shuffle(group_idx) step_cnt_ref = np.count_nonzero(np.diff(group_idx)) assert aggregate_nb_wv.step_count(group_idx) == step_cnt_ref + 1 assert len(aggregate_nb_wv.step_indices(group_idx)) == step_cnt_ref + 2 def test_step_indices_fields(aggregate_nb_wv): group_idx = np.array([1, 1, 1, 2, 2, 3, 3, 4, 5, 2, 2], dtype=int) steps = aggregate_nb_wv.step_indices(group_idx) np.testing.assert_array_equal(steps, np.array([0, 3, 5, 7, 8, 9, 11])) numpy-groupies-0.11.3/numpy_groupies/tests/test_utils.py000066400000000000000000000013371501360642500236050ustar00rootroot00000000000000import numpy as np from ..utils import check_dtype, unpack def test_check_dtype(): dtype = check_dtype(None, "mean", np.arange(10, dtype=int), 10) assert np.issubdtype(dtype, np.floating) def test_unpack(): """Keep this test, in case unpack might get reimplemented again at some point.""" group_idx = np.arange(10) np.random.shuffle(group_idx) group_idx = np.repeat(group_idx, 3) vals = np.random.randn(np.max(group_idx) + 1) np.testing.assert_array_equal(unpack(group_idx, vals), vals[group_idx]) def test_unpack_long(): group_idx = np.repeat(np.arange(10000), 20) vals = np.random.randn(np.max(group_idx) + 1) np.testing.assert_array_equal(unpack(group_idx, vals), vals[group_idx]) numpy-groupies-0.11.3/numpy_groupies/utils.py000066400000000000000000000552771501360642500214200ustar00rootroot00000000000000"""Common functionality for all aggregate implementations.""" import platform import numpy as np aggregate_common_doc = """ See readme file at https://github.com/ml31415/numpy-groupies for a full description. Below we reproduce the "Full description of inputs" section from that readme, note that the text below makes references to other portions of the readme that are not shown here. group_idx: this is an array of non-negative integers, to be used as the "labels" with which to group the values in ``a``. Although we have so far assumed that ``group_idx`` is one-dimensional, and the same length as ``a``, it can in fact be two-dimensional (or some form of nested sequences that can be converted to 2D). When ``group_idx`` is 2D, the size of the 0th dimension corresponds to the number of dimensions in the output, i.e. ``group_idx[i,j]`` gives the index into the ith dimension in the output for ``a[j]``. Note that ``a`` should still be 1D (or scalar), with length matching ``group_idx.shape[1]``. a: this is the array of values to be aggregated. See above for a simple demonstration of what this means. ``a`` will normally be a one-dimensional array, however it can also be a scalar in some cases. func: default='sum' the function to use for aggregation. See the section above for details. Note that the simplest way to specify the function is using a string (e.g. ``func='max'``) however a number of aliases are also defined (e.g. you can use the ``func=np.max``, or even ``func=max``, where ``max`` is the builtin function). To check the available aliases see ``utils.py``. size: default=None the shape of the output array. If ``None``, the maximum value in ``group_idx`` will set the size of the output. Note that for multidimensional output you need to list the size of each dimension here, or give ``None``. fill_value: default=0 in the example above, group 2 does not have any data, so requires some kind of filling value - in this case the default of ``0`` is used. If you had set ``fill_value=nan`` or something else, that value would appear instead of ``0`` for the 2 element in the output. Note that there are some subtle interactions between what is permitted for ``fill_value`` and the input/output ``dtype`` - exceptions should be raised in most cases to alert the programmer if issue arrise. order: default='C' this is relevant only for multimensional output. It controls the layout of the output array in memory, can be ``'F'`` for fortran-style. dtype: default=None the ``dtype`` of the output. By default something sensible is chosen based on the input, aggregation function, and ``fill_value``. ddof: default=0 passed through into calculations of variance and standard deviation (see above). """ funcs_common = "first last len mean var std allnan anynan max min argmax argmin sumofsquares cumsum cumprod cummax cummin".split() funcs_no_separate_nan = frozenset(["sort", "rsort", "array", "allnan", "anynan"]) _alias_str = { "or": "any", "and": "all", "add": "sum", "count": "len", "plus": "sum", "multiply": "prod", "product": "prod", "times": "prod", "amax": "max", "maximum": "max", "amin": "min", "minimum": "min", "split": "array", "splice": "array", "sorted": "sort", "asort": "sort", "asorted": "sort", "rsorted": "sort", "dsort": "sort", "dsorted": "rsort", } _alias_builtin = { all: "all", any: "any", len: "len", max: "max", min: "min", sum: "sum", sorted: "sort", slice: "array", list: "array", } _alias_numpy = { np.add: "sum", np.sum: "sum", np.any: "any", np.all: "all", np.multiply: "prod", np.prod: "prod", np.amin: "min", np.min: "min", np.minimum: "min", np.amax: "max", np.max: "max", np.maximum: "max", np.argmax: "argmax", np.argmin: "argmin", np.mean: "mean", np.std: "std", np.var: "var", np.array: "array", np.asarray: "array", np.sort: "sort", np.cumsum: "cumsum", np.cumprod: "cumprod", np.nansum: "nansum", np.nanprod: "nanprod", np.nanmean: "nanmean", np.nanvar: "nanvar", np.nanmax: "nanmax", np.nanmin: "nanmin", np.nanstd: "nanstd", np.nanargmax: "nanargmax", np.nanargmin: "nanargmin", np.nancumsum: "nancumsum", } def get_aliasing(*extra): """ Assembles a dictionary that maps both strings and functions to a list of supported function names. Examples: alias['add'] = 'sum' alias[sorted] = 'sort' This function should only be called during import. """ alias = dict((k, k) for k in funcs_common) alias.update(_alias_str) alias.update((fn, fn) for fn in _alias_builtin.values()) alias.update(_alias_builtin) for d in extra: alias.update(d) alias.update((k, k) for k in set(alias.values())) # Treat nan-functions as firstclass member and add them directly for key in set(alias.values()): if key not in funcs_no_separate_nan and not key.startswith("nan"): key = "nan" + key alias[key] = key return alias aliasing_py = get_aliasing() aliasing = get_aliasing(_alias_numpy) def get_func(func, aliasing, implementations): """Return the key of a found implementation or the func itself""" try: func_str = aliasing[func] except KeyError: if callable(func): return func else: if func_str in implementations: return func_str if func_str.startswith("nan") and func_str[3:] in funcs_no_separate_nan: raise ValueError(f"{func_str[3:]} does not have a nan-version") else: raise NotImplementedError("No such function available") raise ValueError( f"func {func} is neither a valid function string nor a callable object" ) def check_boolean(x): if x not in (0, 1): raise ValueError("Value not boolean") _next_int_dtype = dict( bool=np.int8, uint8=np.int16, int8=np.int16, uint16=np.int32, int16=np.int32, uint32=np.int64, int32=np.int64, ) _next_float_dtype = dict( float16=np.float32, float32=np.float64, float64=np.complex64, complex64=np.complex128, ) def minimum_dtype(x, dtype=np.bool_): """ Returns the "most basic" dtype which represents `x` properly, which provides at least the same value range as the specified dtype. """ def check_type(x, dtype): try: with np.errstate(invalid="ignore"): converted = np.array(x).astype(dtype) except (ValueError, OverflowError, RuntimeWarning): return False # False if some overflow has happened return converted == x or np.isnan(x) def type_loop(x, dtype, dtype_dict, default=None): while True: try: dtype = np.dtype(dtype_dict[dtype.name]) if check_type(x, dtype): return np.dtype(dtype) except KeyError: if default is not None: return np.dtype(default) raise ValueError(f"Can not determine dtype of {x!r}") dtype = np.dtype(dtype) if check_type(x, dtype): return dtype if np.issubdtype(dtype, np.inexact): return type_loop(x, dtype, _next_float_dtype) else: return type_loop(x, dtype, _next_int_dtype, default=np.float32) def minimum_dtype_scalar(x, dtype, a): if dtype is None: dtype = np.dtype(type(a)) if isinstance(a, (int, float)) else a.dtype return minimum_dtype(x, dtype) _forced_types = { "array": object, "all": bool, "any": bool, "nanall": bool, "nanany": bool, "len": np.int64, "nanlen": np.int64, "allnan": bool, "anynan": bool, "argmax": np.int64, "argmin": np.int64, "nanargmin": np.int64, "nanargmax": np.int64, } if platform.architecture()[0] == "32bit": _forced_types = { "array": object, "all": bool, "any": bool, "nanall": bool, "nanany": bool, "len": np.int32, "nanlen": np.int32, "allnan": bool, "anynan": bool, "argmax": np.int32, "argmin": np.int32, "nanargmin": np.int32, "nanargmax": np.int32, } _forced_float_types = {"mean", "var", "std", "nanmean", "nanvar", "nanstd"} _forced_same_type = { "min", "max", "first", "last", "nanmin", "nanmax", "nanfirst", "nanlast", } def check_dtype(dtype, func_str, a, n): if np.isscalar(a) or not a.shape: if func_str not in ("sum", "prod", "len"): raise ValueError( "scalar inputs are supported only for 'sum', 'prod' and 'len'" ) a_dtype = np.dtype(type(a)) else: a_dtype = a.dtype if dtype is not None: # dtype set by the user # Careful here: np.bool != np.bool_ ! if np.issubdtype(dtype, np.bool_) and not ( "all" in func_str or "any" in func_str ): raise TypeError( f"function {func_str} requires a more complex datatype than bool" ) if not np.issubdtype(dtype, np.integer) and func_str in ("len", "nanlen"): raise TypeError(f"function {func_str} requires an integer datatype") # TODO: Maybe have some more checks here return np.dtype(dtype) else: try: return np.dtype(_forced_types[func_str]) except KeyError: if func_str in _forced_float_types: if np.issubdtype(a_dtype, np.floating): return a_dtype else: return np.dtype(np.float64) else: if func_str == "sum": # Try to guess the minimally required int size if np.issubdtype(a_dtype, np.int64): # It's not getting bigger anymore # TODO: strictly speaking it might need float return np.dtype(np.int64) elif np.issubdtype(a_dtype, np.integer): maxval = np.iinfo(a_dtype).max * n return minimum_dtype(maxval, a_dtype) elif np.issubdtype(a_dtype, np.bool_): return minimum_dtype(n, a_dtype) else: # floating, inexact, whatever return a_dtype elif func_str in _forced_same_type: return a_dtype else: if isinstance(a_dtype, np.integer): return np.dtype(np.int64) else: return a_dtype def minval(fill_value, dtype): dtype = minimum_dtype(fill_value, dtype) if issubclass(dtype.type, np.floating): return -np.inf if issubclass(dtype.type, np.integer): return np.iinfo(dtype).min return np.finfo(dtype).min def maxval(fill_value, dtype): dtype = minimum_dtype(fill_value, dtype) if issubclass(dtype.type, np.floating): return np.inf if issubclass(dtype.type, np.integer): return np.iinfo(dtype).max return np.finfo(dtype).max def check_fill_value(fill_value, dtype, func=None): if func in ("all", "any", "allnan", "anynan"): check_boolean(fill_value) else: try: return dtype.type(fill_value) except ValueError: raise ValueError( f"fill_value must be convertible into {dtype.type.__name__}" ) def check_group_idx(group_idx, a=None, check_min=True): if a is not None and group_idx.size != a.size: raise ValueError("The size of group_idx must be the same as a.size") if not issubclass(group_idx.dtype.type, np.integer): raise TypeError("group_idx must be of integer type") if check_min and np.min(group_idx) < 0: raise ValueError("group_idx contains negative indices") def _ravel_group_idx(group_idx, a, axis, size, order, method="ravel"): ndim_a = a.ndim # Create the broadcast-ready multidimensional indexing. # Note the user could do this themselves, so this is # very much just a convenience. size_in = int(np.max(group_idx)) + 1 if size is None else size group_idx_in = group_idx group_idx = [] size = [] for ii, s in enumerate(a.shape): if method == "ravel": ii_idx = group_idx_in if ii == axis else np.arange(s) ii_shape = [1] * ndim_a ii_shape[ii] = s group_idx.append(ii_idx.reshape(ii_shape)) size.append(size_in if ii == axis else s) # Use the indexing, and return. It's a bit simpler than # using trying to keep all the logic below happy if method == "ravel": group_idx = np.ravel_multi_index(group_idx, size, order=order, mode="raise") elif method == "offset": group_idx = offset_labels(group_idx_in, a.shape, axis, order, size_in) return group_idx, size def offset_labels(group_idx, inshape, axis, order, size): """ Offset group labels by dimension. This is used when we reduce over a subset of the dimensions of group_idx. It assumes that the reductions dimensions have been flattened in the last dimension Copied from https://stackoverflow.com/questions/46256279/bin-elements-per-row-vectorized-2d-bincount-for-numpy """ newaxes = tuple(ax for ax in range(len(inshape)) if ax != axis) group_idx = np.broadcast_to(np.expand_dims(group_idx, newaxes), inshape) if axis not in (-1, len(inshape) - 1): group_idx = np.moveaxis(group_idx, axis, -1) newshape = group_idx.shape[:-1] + (-1,) group_idx = ( group_idx + np.arange(np.prod(newshape[:-1]), dtype=int).reshape(newshape) * size ) if axis not in (-1, len(inshape) - 1): return np.moveaxis(group_idx, -1, axis) else: return group_idx def input_validation( group_idx, a, size=None, order="C", axis=None, ravel_group_idx=True, check_bounds=True, func=None, ): """ Do some fairly extensive checking of group_idx and a, trying to give the user as much help as possible with what is wrong. Also, convert ndim-indexing to 1d indexing. """ if not isinstance(a, (int, float, complex)) and not is_duck_array(a): a = np.asanyarray(a) if not is_duck_array(group_idx): group_idx = np.asanyarray(group_idx) if not np.issubdtype(group_idx.dtype, np.integer): raise TypeError("group_idx must be of integer type") # This check works for multidimensional indexing as well if check_bounds and np.any(group_idx < 0): raise ValueError("negative indices not supported") ndim_idx = np.ndim(group_idx) ndim_a = np.ndim(a) # Deal with the axis arg: if present, then turn 1d indexing into # multi-dimensional indexing along the specified axis. if axis is None: if ndim_a > 1: raise ValueError( "a must be scalar or 1 dimensional, use .ravel to flatten. Alternatively specify axis." ) elif axis >= ndim_a or axis < -ndim_a: raise ValueError("axis arg too large for np.ndim(a)") else: axis = axis if axis >= 0 else ndim_a + axis # negative indexing if ndim_idx > 1: # TODO: we could support a sequence of axis values for multiple # dimensions of group_idx. raise NotImplementedError( "only 1d indexing currently supported with axis arg." ) elif a.shape[axis] != len(group_idx): raise ValueError("a.shape[axis] doesn't match length of group_idx.") elif size is not None and not np.isscalar(size): raise NotImplementedError( "when using axis arg, size must be None or scalar." ) else: is_form_3 = group_idx.ndim == 1 and a.ndim > 1 and axis is not None orig_shape = a.shape if is_form_3 else group_idx.shape if isinstance(func, str) and "arg" in func: unravel_shape = orig_shape else: unravel_shape = None method = "offset" if axis == ndim_a - 1 else "ravel" group_idx, size = _ravel_group_idx( group_idx, a, axis, size, order, method=method ) flat_size = np.prod(size) ndim_idx = ndim_a size = ( orig_shape if is_form_3 and not callable(func) and "cum" in func else size ) return ( group_idx.ravel(), a.ravel(), flat_size, ndim_idx, size, unravel_shape, ) if ndim_idx == 1: if size is None: size = int(np.max(group_idx)) + 1 else: if not np.isscalar(size): raise ValueError("output size must be scalar or None") if check_bounds and np.any(group_idx > size - 1): raise ValueError(f"one or more indices are too large for size {size}") flat_size = size else: if size is None: size = np.max(group_idx, axis=1).astype(int) + 1 elif np.isscalar(size): raise ValueError(f"output size must be of length {len(group_idx)}") elif len(size) != len(group_idx): raise ValueError( f"{len(size)} sizes given, but {len(group_idx)} output dimensions specified in index" ) if ravel_group_idx: group_idx = np.ravel_multi_index(group_idx, size, order=order, mode="raise") flat_size = np.prod(size) if not (np.ndim(a) == 0 or len(a) == group_idx.size): raise ValueError( "group_idx and a must be of the same length, or a can be scalar" ) return group_idx, a, flat_size, ndim_idx, size, None # General tools def unpack(group_idx, ret): """ Take an aggregate packed array and uncompress it to the size of group_idx. This is equivalent to ret[group_idx]. """ return ret[group_idx] def allnan(x): return np.all(np.isnan(x)) def anynan(x): return np.any(np.isnan(x)) def nanfirst(x): return x[~np.isnan(x)][0] def nanlast(x): return x[~np.isnan(x)][-1] def multi_arange(n): """By example: # 0 1 2 3 4 5 6 7 8 n = [0, 0, 3, 0, 0, 2, 0, 2, 1] res = [0, 1, 2, 0, 1, 0, 1, 0] That is it is equivalent to something like this : hstack((arange(n_i) for n_i in n)) This version seems quite a bit faster, at least for some possible inputs, and at any rate it encapsulates a task in a function. """ if n.ndim != 1: raise ValueError("n is supposed to be 1d array.") n_mask = n.astype(bool) n_cumsum = np.cumsum(n) ret = np.ones(n_cumsum[-1] + 1, dtype=int) ret[n_cumsum[n_mask]] -= n[n_mask] ret[0] -= 1 return np.cumsum(ret)[:-1] def label_contiguous_1d(X): """ WARNING: API for this function is not liable to change!!! By example: X = [F T T F F T F F F T T T] result = [0 1 1 0 0 2 0 0 0 3 3 3] Or: X = [0 3 3 0 0 5 5 5 1 1 0 2] result = [0 1 1 0 0 2 2 2 3 3 0 4] The ``0`` or ``False`` elements of ``X`` are labeled as ``0`` in the output. If ``X`` is a boolean array, each contiguous block of ``True`` is given an integer label, if ``X`` is not boolean, then each contiguous block of identical values is given an integer label. Integer labels are 1, 2, 3, ..... (i.e. start a 1 and increase by 1 for each block with no skipped numbers.) """ if X.ndim != 1: raise ValueError("this is for 1d masks only.") is_start = np.empty(len(X), dtype=bool) is_start[0] = X[0] # True if X[0] is True or non-zero if X.dtype.kind == "b": is_start[1:] = ~X[:-1] & X[1:] M = X else: M = X.astype(bool) is_start[1:] = X[:-1] != X[1:] is_start[~M] = False L = np.cumsum(is_start) L[~M] = 0 return L def relabel_groups_unique(group_idx): """ See also ``relabel_groups_masked``. keep_group: [0 3 3 3 0 2 5 2 0 1 1 0 3 5 5] ret: [0 3 3 3 0 2 4 2 0 1 1 0 3 4 4] Description of above: unique groups in input was ``1,2,3,5``, i.e. ``4`` was missing, so group 5 was relabled to be ``4``. Relabeling maintains order, just "compressing" the higher numbers to fill gaps. """ keep_group = np.zeros(np.max(group_idx) + 1, dtype=bool) keep_group[0] = True keep_group[group_idx] = True return relabel_groups_masked(group_idx, keep_group) def relabel_groups_masked(group_idx, keep_group): """ group_idx: [0 3 3 3 0 2 5 2 0 1 1 0 3 5 5] 0 1 2 3 4 5 keep_group: [0 1 0 1 1 1] ret: [0 2 2 2 0 0 4 0 0 1 1 0 2 4 4] Description of above in words: remove group 2, and relabel group 3,4, and 5 to be 2, 3 and 4 respectively, in order to fill the gap. Note that group 4 was never used in the input group_idx, but the user supplied mask said to keep group 4, so group 5 is only moved up by one place to fill the gap created by removing group 2. That is, the mask describes which groups to remove, the remaining groups are relabled to remove the gaps created by the falsy elements in ``keep_group``. Note that ``keep_group[0]`` has no particular meaning because it refers to the zero group which cannot be "removed". ``keep_group`` should be bool and ``group_idx`` int. Values in ``group_idx`` can be any order. """ keep_group = keep_group.astype(bool, copy=not keep_group[0]) if not keep_group[0]: # ensuring keep_group[0] is True makes life easier keep_group[0] = True relabel = np.zeros(keep_group.size, dtype=group_idx.dtype) relabel[keep_group] = np.arange(np.count_nonzero(keep_group)) return relabel[group_idx] def is_duck_array(value): """This function was copied from xarray/core/utils.py under the terms of Xarray's Apache-2 license.""" if isinstance(value, np.ndarray): return True return ( hasattr(value, "ndim") and hasattr(value, "shape") and hasattr(value, "dtype") and hasattr(value, "__array_function__") and hasattr(value, "__array_ufunc__") ) def iscomplexobj(x): """Copied from np.iscomplexobj so that we place fewer requirements on duck array types.""" try: dtype = x.dtype type_ = dtype.type except AttributeError: type_ = np.asarray(x).dtype.type return issubclass(type_, np.complexfloating) numpy-groupies-0.11.3/pyproject.toml000066400000000000000000000034231501360642500175170ustar00rootroot00000000000000[build-system] requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] name = "numpy-groupies" description = "Optimised tools for group-indexing operations: aggregated sum and more." dynamic = ["version"] readme = {file = "README.md", content-type = "text/markdown"} license = {file = "LICENSE.txt"} authors = [ {name = "Michael Löffler", email = "ml@occam.com.ua"}, {name = "Daniel Manson", email = "danielmanson.uk@gmail.com"} ] maintainers = [ {name = "Deepak Cherian", email = "dcherian@ucar.edu"} ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", "License :: OSI Approved :: BSD License", ] keywords = ["accumarray", "aggregate", "groupby", "grouping", "indexing"] requires-python = ">=3.9" dependencies = ["numpy"] [project.optional-dependencies] fast = [ "numba", ] dev = [ "pytest", "numba", "pandas", ] [project.urls] source = "https://github.com/ml31415/numpy-groupies" tracker = "https://github.com/ml31415/numpy-groupies/issues" [tool.black] line-length = 120 [tool.isort] profile = "black" honor_noqa = true [tool.setuptools.packages.find] include = ["numpy_groupies*"] [tool.setuptools_scm] write_to = "numpy_groupies/_version.py" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] [tool.codespell] ignore-words-list = "nd," ignore-regex = ".*codespell-ignore$"